Compare commits

...

1547 Commits

Author SHA1 Message Date
Benjamin Bouvier 4164effbf9 Bump matrix-sdk to 0.7.1 2024-01-22 11:27:03 +01:00
Sami J. Mäkinen 917e8c291e Upgrade aquamarine dependency 2024-01-22 11:25:56 +01:00
Jonas Platte 3e17fc2072 Add a description for matrix-sdk-ui 2024-01-05 14:23:41 +01:00
Jonas Platte f08e978540 Upgrade uniffi
… and specify a version such that publishing of the ui crate becomes possible.

We still use a git dependency for FFI crate builds because there were some
likely important breaking changes that haven't been released.

This is required to publish `matrix-sdk-ui`.
2024-01-05 14:03:44 +01:00
Jonas Platte 40b09cda2f Bump all of the versions to 0.7.0 2024-01-05 12:58:54 +01:00
Jonas Platte 2710a85897 Rename matrix-sdk-base/{Changelog => CHANGELOG.md}
… for consistency.
2024-01-05 12:58:54 +01:00
Jonas Platte 315e6c9d85 Use workspace dependencies for matrix-sdk-test 2024-01-05 12:58:54 +01:00
Benjamin Bouvier 2822f2471a Update crates/matrix-sdk/src/room/mod.rs
Signed-off-by: Benjamin Bouvier <public@benj.me>
2024-01-05 12:12:52 +01:00
Kévin Commaille b0530ba3a6 sdk: Create ReportedContentScore
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2024-01-05 12:12:52 +01:00
Kévin Commaille 844212e965 sdk: Add method to report an event
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2024-01-05 12:12:52 +01:00
Jonas Platte eb33c3754a bindings: Move matrix_sdk_ui enum definitions out of UDL 2024-01-04 17:14:44 +01:00
Richard van der Hoff cf57992346 indexeddb: logging for open sequence (#2983)
I was trying to figure out what was taking so long, so added some logging.
2024-01-04 15:10:54 +00:00
Jonas Platte d9f99f84f5 Fix Cargo warning about deafult-features
`default-features = false` was already a no-op prior to workspace dependency change,
as `matrix-sdk-crypto` has no default features.
2024-01-04 14:54:33 +01:00
Benjamin Bouvier afd05a24df Rename BaseClient::update_summary to BaseClient::set_room_info 2024-01-04 11:41:50 +01:00
Benjamin Bouvier 0f91eebc96 BaseClient::apply_changes doesn't need to be async 2024-01-04 11:41:50 +01:00
Benjamin Bouvier 0f8b99b744 Remove spurious testing guards in the read status integration test 2024-01-04 11:41:50 +01:00
Benjamin Bouvier 30714c3f92 Don't cause a spurious room info update when setting the latest event 2024-01-04 11:41:50 +01:00
Benjamin Bouvier 96b427a332 Add test showing the spurious room info update 2024-01-04 11:41:50 +01:00
Jonas Platte 1c7bf820bf Use workspace dependencies for crates/* dependencies
… except from examples (such that they remain copy-pastable).
2024-01-04 10:02:07 +01:00
Jonas Platte 24b879bbc0 Clean up Cargo manifest formattting 2024-01-04 10:02:07 +01:00
Jonas Platte 51a0bb6f3b ci: Upgrade typos action 2024-01-04 09:53:17 +01:00
Jonas Platte 9eca314511 Fix a typo 2024-01-04 09:53:17 +01:00
Jonas Platte c4724c082e Upgrade dependencies 2024-01-02 19:12:42 +01:00
manuroe 3d9d3b7ca6 Merge pull request #2977 from matrix-org/valere/long_mark_as_sent
Remove an expensive database call only used for tracing prupose
2024-01-02 13:35:36 +01:00
Valere 541d9184c6 remove expensive db call for tracing 2023-12-22 16:19:25 +01:00
Benjamin Bouvier 45bdbf5067 Add test to make sure notification count is taken into account when processing sliding sync 2023-12-22 14:45:54 +01:00
Benjamin Bouvier 04ef3d9f95 Fix: revert notification count change from read receipts PR
This was modified and then removed from the PR, and forgot to put back the previous
implementation.
2023-12-22 14:45:54 +01:00
Benjamin Bouvier 75fe874cae read receipts: don't update a RoomInfo if the read receipts haven't changed 2023-12-21 17:41:35 +01:00
Benjamin Bouvier 2a77aaa068 read receipts: update the API shape of compute_notifications 2023-12-21 15:54:19 +01:00
Benjamin Bouvier 4a686229e1 read receipts: make find_and_count_events a method of RoomReadReceipts 2023-12-21 15:54:19 +01:00
Benjamin Bouvier d64fb8241d read receipts: add test for find_and_count_events 2023-12-21 15:54:19 +01:00
Benjamin Bouvier 870d1eafb4 read receipts: add RoomReadReceipts::reset 2023-12-21 15:54:19 +01:00
Benjamin Bouvier a9905eaedd read receipts: don't count the same action multiple time per event 2023-12-21 15:54:19 +01:00
Benjamin Bouvier f1c15da87d read receipts: make count_unread_and_mention a method of RoomReadReceipts and rename it 2023-12-21 15:54:19 +01:00
Benjamin Bouvier f2b53080b6 read receipts: move RoomReadReceipts to the read_receipts.rs file 2023-12-21 15:54:19 +01:00
Benjamin Bouvier 7fe4e22076 read receipts: test count_unread_and_highlights 2023-12-21 15:54:19 +01:00
Benjamin Bouvier b6dab1a3a5 read receipts: compute_notifications doesn't need to be async 2023-12-21 15:54:19 +01:00
Andy Balaam ec833c81e0 Migrate inbound_group_session2 to fix keys incorrectly copied from old store version (#2957)
In migrate_data_for_v6, we incorrectly copied the keys in inbound_group_sessions verbatim into inbound_group_sessions2. What we should have done is re-encrypt them using the new table name, so we fix that up with a new migration here.

This caused the bug because we were looking for sessions to mark as backed up by calculating their key (from room_id and session_id) but that key did not exist, because the old sessions were stored under the incorrect keys. So no sessions were marked as backed up, and we repeatedly tried to re-mark them.
2023-12-21 14:52:02 +00:00
Ivan Enderlin de0574aa14 Merge pull request #2925 from zecakeh/create-dm-encrypted
client: Allow to create encrypted DM
2023-12-21 14:38:32 +01:00
Benjamin Bouvier fb1ff70538 Disable integration test in code coverage build
The test fails only in the codecov build, not in a local build or in the other integration test.

Needs further investigation.
2023-12-21 11:55:29 +01:00
Benjamin Bouvier d6bcbf2281 Address review comments
- copyright notice
- doc comments and better doc in general
- use static dispatch instead of &dyn T
- other misc comments
2023-12-21 11:55:29 +01:00
Benjamin Bouvier cbc832411d read receipts: add an extra num_unread_notifications field
This helps supporting cases where we want to show that a room has some activity (unread messages) but no notifications.
2023-12-21 11:55:29 +01:00
Benjamin Bouvier 843fcac3c1 read receipts: don't clone cached events for computing the read receipt state
Before this patch, we needed to clone the inner `timeline_queue` and turn it into a concrete `Vec<SyncTimelineEvent>`, just to iterate on the elements,
and because returning an iterator from a trait method is impractical. This now changes it to return the actual concrete type of `timeline_queue`, so
we don't need the extra allocations.

Ideally, matrix-sdk and matrix-sdk-base would be merged, so we don't need to use a trait at all here.
2023-12-21 11:55:29 +01:00
Benjamin Bouvier b3a8f34655 read receipts: move the unread messages and mentions counts to separate fields of RoomInfo 2023-12-21 11:55:29 +01:00
Benjamin Bouvier 73af3d9cfa Make it clear that some functions are tests or test helpers only 2023-12-21 11:55:29 +01:00
Benjamin Bouvier 09e355a7bc read receipts: add integration tests for read receipts 2023-12-21 11:55:29 +01:00
Benjamin Bouvier 9ba09b2ae8 sync: process unread notification count client-side 2023-12-21 11:55:29 +01:00
Benjamin Bouvier c9b02ad068 doc: document SlidingSyncRoomInner::timeline_queue a bit better 2023-12-21 11:55:29 +01:00
Benjamin Bouvier 07c428ec56 sdk base: add latest_read_receipt_event_id field to RoomInfo 2023-12-21 11:55:29 +01:00
Valere d4b8f88e10 Fix regenerate_olm_machine losing backup state (#2961)
* Fix regenerate_olm_machine loosing backup state

* use new helper

* clippy fix

* quick review
2023-12-21 09:43:55 +01:00
Stefan Ceriu 28a58479e7 Bring back original (slower) build style under the sequentially fla… (#2960)
* Bring back original (slower) build style under the `sequentially` flag to work around new build style hanging on older machines

* Apply suggestions from code review

Signed-off-by: Benjamin Bouvier <public@benj.me>

---------

Signed-off-by: Benjamin Bouvier <public@benj.me>
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-12-21 08:15:21 +00:00
Benjamin Bouvier 320b868694 style: remove spurious fully-qualified path 2023-12-18 15:47:45 +01:00
Benjamin Bouvier 8615b1283a sliding sync: don't cause a spurious RoomInfo update
The room_info variable in this function contains the latest_event, and later it's set as the Room's room info (`inner.inner`) field.
Calling `set_latest_event` manually here will cause an update of the `RoomInfo` subscriber, while we're not done processing the full
room, and a room info update will happen anyways later (when the entire room is processed), so this one is spurious and will only
show a partial update of the fields.
2023-12-18 15:47:45 +01:00
Benjamin Bouvier 9a4d539428 sliding sync: optimize finding the room member state event
- We can look up the session meta only once, it's not useful to do it once per event as it's
loop-invariant.
- We can look at the events backwards and stop after the first room membership event we see
in that order.
2023-12-18 15:47:45 +01:00
Kévin Commaille 4d3fc44425 Put imports behind feature
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-18 14:07:01 +01:00
Kévin Commaille 8b878c6591 Fix test
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-18 14:05:01 +01:00
Kévin Commaille 738f7f0336 client: Create DM as encrypted by default
If `e2e-encryption` feature is enabled.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-18 13:04:39 +01:00
Valere f5fb44bd80 Maybe trigger backup at end of sync (#2939)
* maybe trigger backup at end of sync

* add test for backup upload

* missing e2e cfg

* add sliding sync tests

* fix borrow

* fix indent

* fix naming from copy/paste

* Remove extracted function

* fix clippy

* style: inline a few variables only used once into their use sites

---------

Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-12-18 10:41:44 +00:00
Doug e0ba9f5a22 xtask: Use Uniffi Library mode for Kotlin too. 2023-12-18 10:34:38 +01:00
Doug b5aeea0a3b xtask: Handle multiple headers/modulemaps. 2023-12-18 10:34:38 +01:00
Ivan Enderlin 19526cea6b Merge pull request #2928 from zecakeh/qrcode-changes
verification: Expose the `QrVerificationState` and the stream for its changes
2023-12-15 12:52:50 +01:00
Ivan Enderlin 99d0b37914 Merge pull request #2951 from matrix-org/andybalaam/clarify-meaning-of-pendingbackup-sessions
Clarify the meaning of the sessions field in PendingBackup
2023-12-15 12:45:29 +01:00
Andy Balaam b21a438b7e Merge pull request #2934 from matrix-org/andybalaam/mark_sessions_as_backed_up
Provide `CryptoStore::mark_inbound_group_sessions_as_backed_up` on stores and use it in `BackupMachine::mark_request_as_sent`
2023-12-15 11:40:50 +00:00
Andy Balaam f3101baa08 Merge branch 'main' into andybalaam/mark_sessions_as_backed_up 2023-12-15 10:47:36 +00:00
Andy Balaam 0cf99db001 Delete accidentally-copied repeat_vars function
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-15 10:47:11 +00:00
Andy Balaam a032d33d21 Remove comments in favour of a separate PR with type aliases
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-15 10:40:36 +00:00
Andy Balaam 92212cc328 Clarify the meaning of the sessions field in PendingBackup
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-15 10:38:56 +00:00
Kévin Commaille 6ffb0181e4 Make sure qrcode feature is additive
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-15 11:01:03 +01:00
dependabot[bot] 614bf942c0 build(deps): bump zerocopy from 0.7.26 to 0.7.31
Bumps [zerocopy](https://github.com/google/zerocopy) from 0.7.26 to 0.7.31.
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.7.26...v0.7.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 10:39:21 +01:00
Andy Balaam 512509ce8b Switch to using chunk_large_query_over in mark_inbound_group_sessions_as_backed_up
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 16:56:13 +00:00
Andy Balaam dfb33c9534 Move chunk_large_query_over into SqliteObjectExt so it can be reused
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 16:55:36 +00:00
Andy Balaam 03aad4e965 Move repeat_vars into utils so it can be reused
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 16:34:45 +00:00
Andy Balaam 9e67b6fcb1 Merge branch 'main' into andybalaam/mark_sessions_as_backed_up 2023-12-14 15:25:43 +00:00
Ivan Enderlin 9443f455a4 Test Media caching, and re-implement it inside MemoryStore
Test Media caching, and re-implement it inside `MemoryStore`
2023-12-14 15:51:04 +01:00
Ivan Enderlin 384deec1c8 chore(base): Simplify code. 2023-12-14 15:39:24 +01:00
Kévin Commaille 5310f41cda integration-testing: Add test for QR verification
And check room ID in SAS test.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 15:23:16 +01:00
Ivan Enderlin 95a1db8fc7 Merge pull request #2947 from matrix-org/andybalaam/use-correct-limit-in-chunk_large_query_over
Use the limit() method to find the variable limit in chunk_large_query_over
2023-12-14 14:31:40 +01:00
Ivan Enderlin 3d9dffa3b7 chore(base): Remove unused imports. 2023-12-14 14:18:56 +01:00
Andy Balaam 3d7e1d5989 Merge branch 'main' into andybalaam/use-correct-limit-in-chunk_large_query_over 2023-12-14 13:17:06 +00:00
Ivan Enderlin a46bf76d74 test(base): Improve test_media_content.
This patch improves `test_media_content` to ensure that, in case of
multiple medias, only the expected ones are removed. Previously, the
test wasn't testing _other_ medias that should be kept in case of
removals.

This patch continues to improve `test_media_content` to ensure that the
content of the media are the expected ones.

Finally, this patch updates the `MemoryStore` implementation to make
tests happy.
2023-12-14 14:15:01 +01:00
Ivan Enderlin 326935db63 feat(base): Correct implementations for MemoryStore media removal.
This patch rewrites `MemoryStore::add_media_content`,
`::get_media_content`, `::remove_media_content` and
`::remove_media_content_for_uri` to (i) work on `mxc://` URI instead
of “unique key”, and (ii) to handle removal correctly thanks to the new
`RingBuffer::remove` method.
2023-12-14 13:54:12 +01:00
Ivan Enderlin 416bc8b0e4 feat(base): Implement Media::uri.
This new method returns the `MxcUri` associated to the `Media`.
2023-12-14 13:53:23 +01:00
Ivan Enderlin edd113a17c feat(common): Implement RingBuffer::remove. 2023-12-14 13:28:17 +01:00
Ivan Enderlin 66411d7b2e Merge pull request #2946 from matrix-org/andybalaam/tests-for-repeat_vars
Unit tests for the repeat_vars function
2023-12-14 13:10:12 +01:00
Ivan Enderlin 4a53bf1f3d Merge pull request #2945 from matrix-org/andybalaam/limits-access-for-sqlite
Provide a limit() method in sqlite to find limit values
2023-12-14 13:10:02 +01:00
Andy Balaam a3aa55ce91 Use the limit() method to find the variable limit
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 11:31:01 +00:00
Andy Balaam 0066ae6614 Unit tests for the repeat_vars function
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 11:26:43 +00:00
Andy Balaam 17ebc23719 Provide a limit() method in sqlite to find limit values
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-14 11:21:25 +00:00
Ivan Enderlin cdb3449ce2 test(sdk): Test that Media::get_media_content with caching works. 2023-12-14 12:11:24 +01:00
Ivan Enderlin c5b11fc2f8 feat(sdk): Simplify code from Media::get_media_content.
If `use_cache` is true and the cache exists, let's return everything in
one go instead of declaring a `content` variable. It makes the code
easier to read and to understand.
2023-12-14 12:10:08 +01:00
Ivan Enderlin 2ad6acb930 doc(common): Add documentation for RingBuffer::drain. 2023-12-14 12:07:08 +01:00
Ivan Enderlin dac779f4fc test(base): Re-implement a super basic media cache for MemoryStore.
This is a rather over simplistic media cache implementation for the
`MemoryStore`.

It's based on a `RingBuffer` of size 20.

`remove_media_content` pops all medias until the correct one is met (if
it exists). `remove_media_content_for_uri` removes all medias, it
ignores the URI.
2023-12-14 12:04:58 +01:00
Kévin Commaille e2ea19ee77 Add rule kind to error messages
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille cc38768bf4 notification settings: Don't error if poll start rules are not found
These rules are unstable so they might not be
found in every ruleset.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille c87bd4d4ec notification settings: Log errors from requests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille 5700c700f0 error: Use NotificationSettingsError::RuleNotFound's rule ID in display impl
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille fff9882792 notification settings: Rely more on Ruma methods
Simplifies code.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille 45f8ff11c2 notification settings: Derive Copy for enum types
This a good practice for inexpensive types
and avoids to have to call `.clone()` explicitely.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille 7c9d842d05 notification settings: Use private method to get poll start rule ID
A `From` implementation is part of the public API
and this conversion does not make sense outside of the module.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-14 11:39:42 +01:00
Kévin Commaille c3706d7ca0 notification settings: Allow to manage keywords (#2905)
* notification settings: Allow to manage keywords
* Fix wording of docs

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
Signed-off-by: Kévin Commaille <76261501+zecakeh@users.noreply.github.com>
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2023-12-14 11:16:40 +01:00
Ivan Enderlin b314da37b8 feat(ffi): Timeline::send_image and send_video takes an optional thumbnail_url
feat(ffi): `Timeline::send_image` and `send_video` takes an optional `thumbnail_url`
2023-12-14 10:48:16 +01:00
Ivan Enderlin 02ba6c0dbe feat(ffi): Client::*media* are now async
feat(ffi): `Client::*media*` are now async
2023-12-14 09:14:39 +01:00
Ivan Enderlin 22d9c62262 feat(ffi): Timeline::send_image and send_video takes an optional thumbnail_url.
This patch updates `Timeline::send_image` and `Timeline::send_video` so
that `thumbnail_url` is now an `Option<String>`.

The idea is to allow sending an image or a video without a thumbnail.
2023-12-13 17:07:03 +01:00
Ivan Enderlin 18b6387a7a Merge pull request #2935 from matrix-org/rav/fix_nonmonotonic_panic
Configure `Instant` wasm polyfill to use monotonic time
2023-12-13 15:57:32 +01:00
Ivan Enderlin c5ddba2e13 feat(ffi): Client::*media* are now async.
This patch removes `RUNTIME.block_on` inside `Client::get_media_file`,
`::upload_media`, `::get_media_content` and `::get_media_thumbnail`, and
makes those methods async.
2023-12-13 15:33:01 +01:00
Kévin Commaille 9756ed28cf verification: Expose the room ID where the verification is happening
Useful if we want to know in what room
we just sent a VerificationRequest,
with UserIdentity::request_verification

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-13 15:22:13 +01:00
Richard van der Hoff 5701cea51e Configure Instant wasm polyfill to use monotonic time
With the `inaccurate` feature, this polyfill uses `Date.now()` to emulate
`Instant`, which is not monotonic, causing problems like
https://github.com/element-hq/element-web/issues/26416.
2023-12-13 12:08:54 +00:00
Andy Balaam 794b7ead7a Clippy fixes 2023-12-13 08:43:59 +00:00
Andy Balaam f879f3d866 Formatting 2023-12-13 08:32:00 +00:00
Andy Balaam 4ebb8e29b4 Provide CryptoStore::mark_sessions_as_backed_up on stores and use it in BackupMachine::mark_request_as_sent
Signed-off-by: Andy Balaam <andy.balaam@matrix.org>
2023-12-12 16:09:22 +00:00
Doug d764fca7da xtask: Move all the Swift files found
… and fix build swift errors.

- The XCFramework was being built with dylibs which aren't supported on iOS
- The tests build was attempting to generate uniffi from a moved file
2023-12-12 16:51:49 +01:00
Jonas Platte 3b7f9f7361 Reapply "bindings: Use new uniffi-bindgen build mode"
This reverts commit 0d24bcf6e5.
2023-12-12 16:51:49 +01:00
Doug 73770b78bb room: Add test for unban_user. 2023-12-12 16:13:09 +01:00
Doug 7cffc34984 ffi: Support banning, unbanning and kicking users. 2023-12-12 16:13:09 +01:00
Benjamin Bouvier c4ef967523 dx: tweak debugging of user/device pairs missing a session 2023-12-11 17:11:11 +01:00
Kévin Commaille f0b378179e matrix auth: Await save_session_callback
Otherwise the future will not run.
Changes the corresponding test to fail with the old behavior.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-11 16:54:53 +01:00
Kévin Commaille ee344112f3 verification: Expose the QrVerificationState and the stream for its changes
Allows to have a similar API as SasVerification

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-11 16:13:56 +01:00
Benoit Marty 1f52aca210 Fix typo in doc. 2023-12-11 12:13:05 +01:00
Kévin Commaille 74091de8ef sdk: Make sure "testing" feature is enabled for integration tests
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-11 11:53:25 +01:00
Kévin Commaille 0f6efc391a client: Allow to create encrypted DM
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-11 11:14:19 +01:00
Mauro c707e1f17e feat(bindings): expose a function to send private read receipts (#2906) 2023-12-11 09:29:06 +00:00
Benjamin Bouvier 5ab69f7400 ffi: add emoji indices in the session verification data + use decimals as a fallback 2023-12-08 14:48:48 +01:00
Jonas Platte e652069896 sdk: Use crates.io release of mas-oidc-client 2023-12-07 17:34:36 +01:00
Benjamin Bouvier 5337c9d9ea refactoring: make it clear that notifications aren't stored in the state store
`notifications` were stored in the `StateChanges` struct, which made me think that they're then persisted in the database. It's not the case, they
were just stored there by convenience. This commit changes it to a parameter that's passed, as it's not too invasive and clearer that this is only
transient data.
2023-12-07 16:56:18 +01:00
Benjamin Bouvier 9fd52e5df7 timeline: document event_filter better 2023-12-07 10:35:42 +01:00
Benjamin Bouvier 4ab79a4085 timeline: add the room version in the event_filter parameters 2023-12-07 10:35:42 +01:00
Benjamin Bouvier a132c0a885 read receipts: apply a default event filter only for events that get rendered 2023-12-07 10:35:42 +01:00
Benjamin Bouvier 5081802177 room: try restoring from a key backup during backpagination too 2023-12-05 16:53:06 +01:00
Stefan Ceriu 736188811f ffi: expose the call member state event type for clients to check if users can join calls 2023-12-05 15:51:28 +01:00
Benoit Marty 1337fdf0b8 ffi: Expose is_invite_for_me_enabled and set_invite_for_me_enabled in notification settings 2023-12-04 17:04:19 +01:00
Ivan Enderlin 0573137835 Merge pull request #2902 from matrix-org/rav/outgoing_key_requests_switch
Crypto: add new methods for turning off room key requests
2023-12-04 15:12:33 +01:00
Ivan Enderlin 21ce2b07a7 Merge pull request #2904 from matrix-org/jme/add-typing-notice-bindings
Add FFI bindgings for `Room.typing_notice()`
2023-12-04 15:09:46 +01:00
Richard van der Hoff 6451a9dffe rename toggle -> set 2023-12-04 12:53:02 +00:00
Kévin Commaille a6af31984a Assert that the stream is pending
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Kévin Commaille 2de45bcb51 notification settings: Do not expose NotificationSettings::new in the public API
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Kévin Commaille 7bdc1448bd notification settings: Drop event handler only when last instance is dropped
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Kévin Commaille 511c44c588 Add test for NotificationSettings::subscribe_to_changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Kévin Commaille a915900580 notification settings: Allow to subscribe to changes
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Kévin Commaille e12f6fcbb7 notification settings: Accept any type that implements AsRef<str> as rule_id
Simplifies the use of this API with the Predefined{*}RuleId Ruma types.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-12-04 13:30:43 +01:00
Jorge Martín 72dfd3d1fd Add FFI bindgings for Room.typing_notice() 2023-12-01 16:19:32 +01:00
Richard van der Hoff a6c206118d changelog 2023-12-01 12:50:43 +00:00
Richard van der Hoff 2536373546 Add new methods for turning off room key requests 2023-12-01 12:47:44 +00:00
Jorge Martín 045d94ab4b Rename message inside an error, since it clashes with Kotlin's default message property in exceptions 2023-12-01 12:07:40 +01:00
Benjamin Bouvier d8c02d7e55 timeline builder: tweak comments 2023-12-01 11:05:53 +01:00
Benjamin Bouvier b20313c492 read receipts: rename ReadReceipts fields
The "read_receipts" suffix isn't useful since we're looking at the fields of `ReadReceipts` already.
2023-12-01 11:05:53 +01:00
Benjamin Bouvier c3ea2c3736 Format with rustfmt nightly. 2023-12-01 11:05:53 +01:00
Benjamin Bouvier 2c1377b2b5 read receipts: a few function renamings and add getter/setter for receipts on events 2023-12-01 11:05:53 +01:00
Benjamin Bouvier bb85af9279 read receipts: add getter/setter for the latest read receipts cache 2023-12-01 11:05:53 +01:00
Benjamin Bouvier 58ab6704ff read receipts: a few function renamings
Add the prefix `load` to make it clear this may load from the storage.
2023-12-01 11:05:53 +01:00
Benjamin Bouvier 94a64296d1 Show the wrapped error when displaying a SlidingSync::JoinError 2023-11-30 19:52:20 +01:00
Benjamin Bouvier 7d6c16956f fix: reify reformat of missing_session_devices_by_user
`itertools::format` is known to cause issues when its display implementation is being
used multiple times, as it consumes the iterator it was given (and that can only happen once,
unless caching it). This is bad, as our production apps may have multiple subscribers they will
run into a panic.

The fix is to reify the debug string before it's logged, so the tracing consumer will only see
a string, and not the display implementation that would panic on the second use.
2023-11-30 19:52:20 +01:00
Damir Jelić 916bf69e5c Enable backups and set the backup download strategy for the bindings 2023-11-30 16:51:06 +01:00
Damir Jelić 915c10e1b4 Test for the one-by-one download of room keys 2023-11-30 16:20:51 +01:00
Damir Jelić 5e122c5c5e Add a background task which downloads room keys from the backup one-by-one 2023-11-30 16:20:51 +01:00
Damir Jelić 49e5461ef7 Add a method to check if we have a room key stored locally 2023-11-30 16:20:51 +01:00
Damir Jelić 27927c676d Allow the to create a FailuresCache with different values for the max timeout 2023-11-30 16:20:51 +01:00
Damir Jelić ac5a9e106b Move the FailuresCache into the common crate 2023-11-30 16:20:51 +01:00
Damir Jelić 94c4e685fc Test for recovery 2023-11-30 15:12:22 +01:00
Damir Jelić f248e272e9 Expose the recovery stuff in the bindings 2023-11-30 15:12:22 +01:00
Damir Jelić 97026fc3a6 Recovery support
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-11-30 15:12:22 +01:00
Richard van der Hoff 1fcd5af526 indexeddb: Update storage for inbound_group_sessions (#2885)
Currently, querying for inbound group sessions which need backing up is very
inefficient: we have to search through the whole list.

Here, we change the way they are stored so that we can maintain an index of the
ones that need a backup.

Fixes: https://github.com/vector-im/element-web/issues/26488
Fixes: https://github.com/matrix-org/matrix-rust-sdk/issues/2877

---

* indexeddb: Update storage for inbound_group_sessions

Currently, querying for inbound group sessions which need backing up is very
inefficient: we have to search through the whole list.

Here, we change the way they are stored so that we can maintain an index of the
ones that need a backup.

* Rename functions for clarity

* Remove spurious log line

This was a bit verbose

* Rename constants for i_g_s store names

* improve log messages

* add a warning

* Rename `InboundGroupSessionIndexedDbObject.data`

* formatting
2023-11-30 12:01:20 +01:00
Benjamin Bouvier 8b04db666c Remove room.rs file added by mistake
Thanks @jaller94 for noticing and letting me know :)
2023-11-30 10:27:25 +01:00
Timo 1ff2c5bb3e Add recommended vscode settings in contrib/ide 2023-11-28 13:41:04 +01:00
Jonas Platte 05f0106e06 ui: Improve logging for sync update processing 2023-11-27 18:55:21 +01:00
Jonas Platte fd0f369f75 ui: Add extra tracing spans for reactions 2023-11-27 18:55:21 +01:00
Jonas Platte 39fc283353 ui: Improve logging for redactions 2023-11-27 18:55:21 +01:00
Jonas Platte 3ebd8afa49 ui: Add more logging for Timeline::retry_send 2023-11-27 18:55:21 +01:00
Jonas Platte 2483ba2cc6 ui: Raise log level for local events
Local events don't happen as often, so we can afford a higher log level.
2023-11-27 18:55:21 +01:00
Jonas Platte 2d3a458a08 ui: Improve logging for sending attachments 2023-11-27 18:55:21 +01:00
Jonas Platte 246a128ec3 ui: Add logging for send-event cancellation 2023-11-27 18:55:21 +01:00
Jonas Platte 932f12e76d ui: Improve logging for timeline resets 2023-11-27 18:55:21 +01:00
Richard van der Hoff bfe79468c6 Indexeddb: Groundwork for fixing inbound_group_session lookups (#2884)
A set of non-functional changes which lay some groundwork in preparation for fixing vector-im/element-web#26488.
2023-11-27 15:59:49 +00:00
Timo 9503eb49c7 ffi: Expose power level overwrites on room creation 2023-11-27 14:38:35 +00:00
Marco Romano ded854425a timeline: Add poll history API
Allow to retrieve the Poll history of a Room.
The poll history is a Timeline instance that filters only on poll events.
2023-11-27 14:28:40 +00:00
Jonas Platte 959e90252b ffi: Create separate timeline object, mirroring the Rust API 2023-11-27 11:55:48 +01:00
Jonas Platte e761ad8f97 ffi: Remove remove_timeline method
It was somewhat of a footgun because it affected the cached timeline in
`RoomListItem`s as well and is not used anywhere anymore.
2023-11-27 11:55:48 +01:00
Jonas Platte ceeb5e78b6 ffi: Move more things into ruma module 2023-11-24 19:25:44 +01:00
Jonas Platte 04c4284b33 ffi: Split timeline into smaller modules 2023-11-24 19:25:44 +01:00
Jonas Platte bae191b4ed ffi: Move Ruma wrappers / extension traits to new module 2023-11-24 19:25:44 +01:00
Damir Jelić ea2e85c5f5 feat: Support for server-side key backups #2666 2023-11-24 18:16:42 +01:00
Damir Jelić d6401ef278 When disabling backups first delete it from the server 2023-11-24 18:01:05 +01:00
Damir Jelić 9bba437fdd Simplify the secret inbox handling for backups 2023-11-24 18:01:05 +01:00
Damir Jelić 19e65c05cf Remove the CheckingIfUploadNeeded UploadState variant 2023-11-24 18:01:05 +01:00
Damir Jelić 369ca7024f Apply suggestions from code review
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-11-24 18:01:05 +01:00
Damir Jelić e958b1ce28 Don't run the event handler examples 2023-11-24 18:01:05 +01:00
Damir Jelić 4912cd8a40 Typos please 2023-11-24 18:01:05 +01:00
Damir Jelić c99b0e8344 Fix some clippy warnings 2023-11-24 18:01:04 +01:00
Damir Jelić b38f501902 Add an example for the room key backup support 2023-11-24 17:59:00 +01:00
Damir Jelić f37467f81f Add tests for backups 2023-11-24 17:59:00 +01:00
Damir Jelić 6239231ba0 Try to resume backups if we restore the client
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-11-24 17:59:00 +01:00
Damir Jelić aa1623b891 Add a hack so the timeline retries to decrypt if we receive room keys from backup 2023-11-24 17:59:00 +01:00
Damir Jelić 99131d0d7a Fetch the backup recovery key when we import all known secrets 2023-11-24 17:59:00 +01:00
Damir Jelić c5c62d8fda Add a client task that will upload room keys to the backup 2023-11-24 17:58:58 +01:00
Damir Jelić b909f4400d Add support for backups 2023-11-24 17:56:09 +01:00
Damir Jelić 18d69f7515 Add a ChannelObservable 2023-11-24 17:53:33 +01:00
Richard van der Hoff 456d8bb4f2 Reduce logspam during encryption (#2859)
A few different changes to reduce the number of lines that get logged during an
encryption operation.
2023-11-24 14:21:01 +00:00
Jonas Platte b277423237 sdk: Upgrade mas-oicd-client 2023-11-24 15:01:17 +01:00
Jonas Platte 4621dd4317 ffi: Upgrade opentelemetry 2023-11-24 15:01:17 +01:00
Jonas Platte 17797da71a indexeddb: Upgrade dependencies 2023-11-24 15:01:17 +01:00
Jonas Platte eec4227dfc sdk: Upgrade async-related dependencies 2023-11-24 15:01:17 +01:00
Jonas Platte 64ddfd872c sqlite: Upgrade rusqlite / deadpool-sqlite 2023-11-24 15:01:17 +01:00
Jonas Platte 19a990ad41 Update Cargo.lock
The reqwest update is held back to avoid linking issues on iOS.
2023-11-24 15:01:17 +01:00
Jonas Platte 53ad6f5fe5 Upgrade itertools 2023-11-24 15:01:17 +01:00
Jonas Platte 8038414de2 Raise minimum version for Ruma
Otherwise the matrix-sdk crate can be used with an older version of
ruma-client-api which uses different types for a sliding sync type.

The sliding sync breaking change was allowed to be part of a patch
release because sliding sync is an unstable feature in Ruma.
2023-11-24 15:01:17 +01:00
Jonas Platte 560d71ceea sdk: Implement a custom event formatter for javascript logging
Somehow `fmt::pretty` manages to be both overcomplicated and not flexible
enough.

The driver here is that I want to put the event "fields" on a separate line to
the message.
2023-11-24 13:48:35 +01:00
Richard van der Hoff 6591a6ef04 Apply suggestions from code review
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-11-24 12:35:36 +00:00
Benjamin Bouvier 35bac2a6c3 test: add an integration test for left rooms 2023-11-23 15:17:26 +01:00
Benjamin Bouvier 7455b90d24 Tweak debug message wording for a timeline event
Probably from a bad copy-pasta with a poll event.
2023-11-23 15:11:54 +01:00
Benjamin Bouvier 1e359576ad room list service: add a new filter to get all rooms but the left ones
This allows to get a list of all the rooms except for left ones, since they're still part of a sliding sync server response.
2023-11-23 15:06:00 +01:00
Alfonso Grillo fe02752f29 fix: EventTimelineItem.is_editable() respects poll’s preconditions for editing (#2875)
This PR fixes the `EventTimelineItem.is_editable()` function for polls.

Before this changes it always returned false.
Now it consider poll's preconditions for editing:
- The poll has no votes yet
- The poll hasn't an end event
2023-11-23 14:03:25 +00:00
Jonas Platte aa7d2a21a3 test: Fix new clippy lints 2023-11-23 14:15:21 +01:00
Jonas Platte 337f2ad415 Upgrade nightly toolchain used for ci, xtask 2023-11-23 14:15:21 +01:00
Jonas Platte c5c3850edf Use the same nightly toolchain for all xtask commands 2023-11-23 14:15:21 +01:00
Richard van der Hoff 086e988e68 crypto: Be consistent about /keys/query endpoint name
Sometimes, we called this `keys query`, sometimes `/keys/query`, and sometimes,
just for variety, `keys/query`. Generally in Matrix we talk about `/keys/query`
so let's standardise on that.
2023-11-22 14:02:39 +00:00
Val Lorentz 5c37acb81c sdk: Add method to get the set of parent spaces of a room 2023-11-22 14:31:52 +01:00
Richard van der Hoff 2aaa709b0e Reinstate tracing instrumentation on ReadOnlyDevice::encrypt (#2873)
Until https://github.com/matrix-org/matrix-rust-sdk/pull/2862,
`GroupSessionManager::encrypt_session_for` called `Device::encrypt` (via
`Device::maybe_encrypt_room_key`.

`Device::encrypt` is a thin wrapper for `ReadOnlyDevice::encrypt`, but it also
has an `instrument` annotation.

https://github.com/matrix-org/matrix-rust-sdk/pull/2862 short-cuts
`Device::encrypt` and calls `ReadOnlyDevice::encrypt` directly: that was
functionally fine but of course means that we no longer benefit from the
`instrument` annotation.

This PR rectifies the situation by pushing the annotation down to
`ReadOnlyDevice::encrypt`. It also adds some documentation for that function,
since we are using it in more places now.

(Longer-term, I think we should probably aim to get rid of `Device::encrypt`
altogether, but that's a refactor I don't want to take on today.)
2023-11-22 13:04:36 +00:00
Alfonso Grillo 8fe501c4ed ui: Add poll editing API 2023-11-22 13:46:37 +01:00
Stefan Ceriu 79c020f169 Switch from building each target individually to letting cargo figure out the optimal way 2023-11-22 12:33:26 +01:00
Damir Jelić bb4254b9c9 Prepare our /keys/claim response handling to handle multiple OTKs (#2870)
This is useful if we ever decide to switch to X3DH for the session
establishment. It also refactors a bit the /keys/claim response handling
method.

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-11-22 10:41:08 +01:00
Damir Jelić ee0010e831 Make the mocks in some notification tests a bit more specific
The mocks in some notification tests mock any PUT/DELETE request without
the request to hit a certain path. These mocks then might respond to
unintended new requests the client might make.

The tests also assert a bunch of things manually instead of using
expectations when building the mock and calling server.verify().
2023-11-22 10:02:32 +01:00
Richard van der Hoff c9ddf92e25 Disable coverage checking for js_tracing 2023-11-22 07:37:21 +00:00
Jonas Platte c7712b52e6 crypto: Use Option instead of JsOption
We don't need to distinguish null from an absent field in any of the
places we used JsOption, so a regular Option is better.
2023-11-21 16:35:07 +01:00
Richard van der Hoff ead29388f2 crypto: Add instrument annotations to various methods 2023-11-21 14:31:41 +00:00
Benjamin Bouvier 06c9a1a355 Update crates/matrix-sdk/src/sliding_sync/room.rs
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-11-21 14:50:04 +01:00
Benjamin Bouvier 0ed4a9950c test: add a unit test for the group avatar being set and unset 2023-11-21 14:50:04 +01:00
Benjamin Bouvier 844340e0f7 sliding sync: fix unsetting the avatar in a sliding sync room
The meaning of "null" and "undefined" were conflated by Ruma, previously. This updates to the latest Ruma, which changed the `avatar` type from
`Option` to `JsOption` and thus allowed us to distinguish both cases: `null` means the avatar has been unset, `undefined` means it's not changed
since the previous request.
2023-11-21 14:50:04 +01:00
Benjamin Bouvier 692be61043 test: add an integration test for avatar in group conversations 2023-11-21 14:50:04 +01:00
Jonas Platte d0b6771251 crypto: Simplify double reference handling 2023-11-21 13:15:27 +01:00
Jonas Platte 47298b9200 test: Fix indentation 2023-11-21 13:15:27 +01:00
Jonas Platte a262cb23d9 Fix rustc, clippy warnings 2023-11-21 13:15:27 +01:00
Richard van der Hoff a881cd0712 crypto: Improve logging in receive_keys_claim_response 2023-11-21 11:40:48 +01:00
Richard van der Hoff c536760fc1 Improve performance of share_room_key (#2862)
Encryption doesn't usually require access to the user identity, so we can skip loading that from the store.

Unfortunately that means a fair bit of refectoring, in the form of replacing Device with ReadOnlyDevice.

This reduces the time taken to encrypt a message in a large room from about 3 seconds to 1 second.
2023-11-20 18:46:58 +00:00
Timo 894f4c218d element call: Remove E2EEenabled flag (#2847)
This is a deprecated flag for element call using livekit. It was used to enable/disable matrix signalling event encryption. This is not relevant for embedded mode at all since there the hosting client is taking care of encryption.

---

* Remove E2EEenabled flag from the rust sdk.
This is a deprecated flag for element call using livekit. It is replaced
by the `password` and the `perParticipantE2EE` flag.
If `perParticipantE2EE` is set to `false`
and there is now password encryption is disabled implicitly.

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

* `cargo +nightly fmt`

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

* change test based on review

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

* Apply suggestions from code review

---------

Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-11-20 16:43:33 +01:00
Jonas Platte 6472ba5109 Upgrade UniFFI 2023-11-20 11:43:05 +01:00
Benjamin Bouvier bcf33c0bcb Test that a live read receipt update received in sliding sync updates the timeline 2023-11-20 10:53:38 +01:00
Benjamin Bouvier fe541bc601 sliding sync: include read receipts into room updates so they're handled by the timeline too 2023-11-20 10:53:38 +01:00
Richard van der Hoff 2d3ee89477 delint, again 2023-11-19 14:50:02 +00:00
Richard van der Hoff cb1827f151 delint 2023-11-17 18:37:57 +00:00
Richard van der Hoff 4299697935 Implement a custom event formatter for javascript logging
Somehow `fmt::pretty` manages to be both overcomplicated and not flexible
enough.

The driver here is that I want to put the event "fields" on a separate line to
the message.
2023-11-17 18:30:21 +00:00
Richard van der Hoff 7e53c6821b Improve performance of get_missing_sessions (#2845)
Two sets of improvements to `get_missing_sessions` here:

 * Currently, for any users who have an empty device list, we call `KeyQueryManager::wait_if_user_key_query_pending`, which waits up to 5 seconds for a `/keys/query` response. (Arguably, we should be waiting longer: it is not unusual for such a request to take a while.)

   The problem is that that user's server may have been blacklisted, in which case we won't even be trying to do `/keys/query` resquests for that user, so we'll be waiting for something that never happens.

   To fix this, let's check the failures list when deciding if we should wait for a user's devices.

 * Separately, but closely related: for each user thus affected, we do the wait *in series*. This is a bit silly: there is no point waiting 50 times. We can parallelise the work.

Fixes #2793.

---

* Move `get_user_devices` to `IdentityManager`

... since it's going to need to interact with the `FailuresCache` which is
stored there.

* `get_user_devices_for_encryption`: don't wait for failed servers

* `get_missing_sessions`: wait for users in parallel

Rather than waiting for each pending user in series, do all the waits in
parallel.

* Changelog

* Update crates/matrix-sdk-crypto/src/identities/manager.rs

* Address review comments
2023-11-17 12:45:16 +01:00
Benjamin Bouvier f21a9b704b doc: update doc comment for OlmMachine::receive_sync_changes and mention it in the changelog
Fixes #2846.

Update crates/matrix-sdk-crypto/src/machine.rs

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-11-16 16:53:21 +01:00
Jonas Platte cb08e5a103 crypto: Fix some typos 2023-11-16 15:58:28 +01:00
Jonas Platte e43a25a703 Avoid verbose string conversions when logging / printing 2023-11-16 15:58:28 +01:00
Jonas Platte ee9af5f501 crypto: Remove redundant tracing event field
Already logged as part of the parent span.
2023-11-16 15:58:28 +01:00
Jonas Platte f9aeb590e0 crypto: Generalize encryption methods 2023-11-16 15:58:28 +01:00
Jonas Platte 2d2eef8b0e crypto: Improve some documentation strings 2023-11-16 15:58:28 +01:00
Jonas Platte bfd2a1c445 Upgrade ruma crates to pull in bugfixes
Includes the following bugfixes:

- Fix the name of the fallback text field for extensible events in
  `RoomMessageEventContentWithoutRelation::make_reply_to_raw()`
- Allow to deserialize `(New)ConditionalPushRule` with a missing `conditions`
  field
- Fix deserialization of `claim_keys` responses without a `failures` field
2023-11-16 11:06:19 +01:00
Timo 6e5682d8d2 ffi: Add get_element_call_required_permissions 2023-11-15 12:00:53 +01:00
Alfonso Grillo 4a428b4731 Update ruma-events 2023-11-15 11:49:42 +01:00
Mauro c24830d794 ffi: Add a method to check if MSC 4028 is enabled on the homeserver 2023-11-15 09:30:23 +00:00
Richard van der Hoff 5786b1a631 Update changelog for matrix-sdk-crypto (#2837)
- Add an entry which I forgot for #2805

 - Reorder a few other items that have been added since
 https://github.com/matrix-org/matrix-rust-sdk/pull/2591.
2023-11-10 12:19:40 +00:00
Val Lorentz 445bf3b02a Add missing "room_id" to test_json::MEMBERS
It doesn't matter at the moment as the only test using `test_json::MEMBERS`
does not rely on the event being valid, but it shows this error
nonetheless:

```
2023-11-10T08:54:29.920782Z DEBUG receive_members{room_id="!hIMjEx205EXNyjVPCV:localhost"}: matrix_sdk_base::client: Failed to deserialize member event: missing field `room_id` at line 1 column 297 event_id="$151800140517rfvjc:localhost"
```

and https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3roomsroomidmembers
says it is a required key.
2023-11-10 09:35:47 +00:00
Richard van der Hoff 71a2d23bf3 Handle missing devices in /keys/claim responses (#2805)
Keep a record of devices that were included in a /keys/claim request, and then, if they are missing in the response, register them as "failed".
2023-11-09 18:30:10 +00:00
Damir Jelić 955d611aaa Bump our deps so we pull in the new release of ruma-client-api 2023-11-09 17:40:03 +01:00
Ivan Enderlin 0f4a175a99 Merge pull request #2833 from matrix-org/mauroromito/notification_has_mention
feat (bindings): `has_mention` in `NotificationItem`
2023-11-09 11:39:33 +01:00
Mauro b553dbb31c Merge branch 'main' into mauroromito/notification_has_mention 2023-11-09 11:14:56 +01:00
Benjamin Bouvier 1abe039582 style: decrease indent in GroupSessionManager::mark_request_as_sent 2023-11-09 11:01:50 +01:00
Benjamin Bouvier 6c490b7aec crypto: inline GroupSessionCache::get_with_id into its single callsite 2023-11-09 11:01:50 +01:00
Benjamin Bouvier c17acd5fe3 crypto: remove direct usage of GroupSessionCache::sessions_being_shared outside the struct 2023-11-09 11:01:50 +01:00
Benjamin Bouvier 8c05d09e4d crypto: remove usage of GroupSessionCache::sessions fields in external users 2023-11-09 11:01:50 +01:00
Jonas Platte 72254caf08 sdk: Enable indexeddb in docsrs feature
… so `ClientBuilder::indexeddb_store` is visible on docs.rs.
2023-11-09 09:25:39 +01:00
Mauro Romito b232cd37e8 implement has_mentions 2023-11-08 17:53:06 +01:00
Jonas Platte 6baf092bc8 sdk: Exclude query parameters from logging
Unfortunately there are a few requests like
check_registration_token_validity that include secrets in the query
parameters.
2023-11-08 10:58:42 +01:00
Jonas Platte c109119b35 sdk: Clean up http_client logging
- Log URI instead of separate homeserver, path
- Always log query parameters (not just for sliding sync)
- Only log request size for requests that could have a body based on the
  HTTP verb
2023-11-08 09:33:39 +01:00
Benjamin Bouvier 8bce5e2416 oidc: add custom hex display to SessionHash
The most important thing is that it's stable and doesn't miss any byte.
2023-11-07 16:44:27 +01:00
Benjamin Bouvier a03b29ac70 oidc(style): avoid qualifying module name when not needed 2023-11-07 16:44:27 +01:00
Benjamin Bouvier c7f7941734 oidc: use hex logging for the hashed oidc tokens 2023-11-07 16:44:27 +01:00
Kévin Commaille 8895ce40d1 Add test for PaginationOptions::until_num_items
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-11-07 12:36:01 +01:00
Kévin Commaille edf32e8941 Store back-pagination tokens in same order as timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-11-07 12:36:01 +01:00
Kévin Commaille 73ddd34cb1 Make several requests if the back-pagination token is not updated
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-11-07 12:36:01 +01:00
Kévin Commaille 0916c93641 ui: Update back-pagination token with the first or last event added
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-11-07 12:36:01 +01:00
Kévin Commaille 8a65e32e7e ui: Update back-pagination token even if chunk is empty or event fails to deserialize
Otherwise back-pagination goes in a loop because the token is never correct

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-11-07 12:36:01 +01:00
Jonas Platte 91c4bc0b3e Upgrade tracing-opentelemetry 2023-11-06 18:41:48 +01:00
Jonas Platte 6f710063b5 Upgrade mas-oidc-client 2023-11-06 18:41:48 +01:00
Jonas Platte e788a6b099 ffi: Simplify Drop implementation for Client 2023-11-06 16:16:40 +01:00
Jonas Platte 8c1baf6ba8 crypto-ffi: Simplify ManuallyDrop usage 2023-11-06 16:16:40 +01:00
Damir Jelić 99032511aa Order the changelog, new entries at the top 2023-11-06 15:17:39 +01:00
Damir Jelić 645bbd67df Remove a unused error type 2023-11-06 14:19:12 +01:00
Damir Jelić 26ede608b0 Add a method which checks if a backup decryption key matches a backup info 2023-11-06 14:19:12 +01:00
Damir Jelić 554aa0404a Add a higher level method to decrypt backed up room keys 2023-11-06 14:19:12 +01:00
Damir Jelić 3438e7de9b Add a higher level method to sign backup versions (#2819) 2023-11-06 12:50:51 +00:00
Damir Jelić 44bb9a3d8f Add a method to create an ExportedRoomKey from a BackedUpRoomKey 2023-11-06 13:41:57 +01:00
Damir Jelić bc5b190509 Add a method to get the RoomKeyBackupInfo to the BackupDecryptionKey 2023-11-06 12:41:22 +01:00
Damir Jelić 2e20f00d99 Add an example for the secret storage support 2023-11-06 10:41:44 +01:00
Damir Jelić 760053b15d Add a test for the secret storage support in the main crate 2023-11-06 10:41:44 +01:00
Damir Jelić e599fa1ccf Add secret storage support to the main SDK crate 2023-11-06 10:41:44 +01:00
Jonas Platte c7fbfd4db8 ffi: Add MediaFileHandle::persist 2023-11-06 10:32:11 +01:00
Jonas Platte ad392a1977 sdk: Add MediaFileHandle::persist 2023-11-06 10:32:11 +01:00
Jonas Platte 57136247bf ffi: Add use_cache parameter to get_media_file 2023-11-06 10:32:11 +01:00
Damir Jelić e5b06bd6d8 Enable backups in the crypto crate by default 2023-11-03 19:17:10 +01:00
Damir Jelić beb01eacfa fixup! Use the better Signatures type in the MegolmV1BackupKey type 2023-11-03 17:01:47 +01:00
Damir Jelić 1e9fab1e4e Use the better Signatures type in the MegolmV1BackupKey type 2023-11-03 17:01:47 +01:00
Benjamin Bouvier 1be7fab4fd ffi: have the (ffi) NotificationClient keep the (ffi) Client alive
Otherwise, it's possible for the `NotificationClient` to be destroyed *after* the ffi `Client`, and then the hack introduced in the previous commit won't work.
2023-11-03 16:54:54 +01:00
Benjamin Bouvier ad5761bfb5 fix(ffi): don't leak Client instances
A detached task was spawned to react upon session changes, and that task captured a clone of the current `Client`.
This caused a leak of the `Client`, because that task would never get aborted, and would not stop by itself.
The fix here consists in having `Client::set_delegate` return a task handle that needs to be stashed by the FFI
users, and cancelled when the Client gets out of scope. This fixes the leak, by removing the last reference onto
the Client.

Then, when dropping the Client, we have to drop the Stores in it. These stores may be sqlite-based stores, which
make use of deadpool. Deadpool has a sync wrapper that will call `block_on` in a `drop` method, and as such it
requires to be in the scope of a tokio runtime to run properly. To avoid breaking all abstractions and giving
access to the inners of the `Client`, the hack used here to properly be in a runtime when dropping the stores is
to replace the inner sdk `Client` in the FFI `Client::drop` method (and replace it with a dummy client that is
minimally configured and will use in-memory stores).
2023-11-03 16:54:54 +01:00
Damir Jelić efb72063ac Add a backup specific method to import room keys 2023-11-03 15:34:11 +01:00
Jonas Platte 9ef6103912 Insert strategic Box::pin to reduce async stack size 2023-11-03 12:23:59 +01:00
Jonas Platte 71c6b98d6a crypto: Box inner field of Account
This reduces its stack size to less than a third of what it previously
was and thus helps with async fn stack size problems.
2023-11-03 12:23:59 +01:00
Jonas Platte dc86835ae2 base: Box large RoomInfo fields
RoomInfo is often passed around by value, including in futures where
holding types with a large stack size is especially problematic¹.
It might make sense to move the actual data of (Base)RoomInfo into
an inner struct that is always held inside a Box or Arc, but this change
should have most of the benefits of that while being a bit simpler.

¹ https://github.com/rust-lang/rust/issues/69826
2023-11-03 12:23:59 +01:00
Benjamin Bouvier ab4c524212 crypto(perf): don't hold the cache lock while waiting on a user key query (#2806)
* crypto(fix): don't hold the cache lock while waiting on a user key query

Fixes #2802. The lock was only useful to sync the database and the in-memory cache for the users awaiting a key query request.
So it's possible to slightly tweak the API by moving the method from `SyncedKeyQueryManager` to non-synced `KeyQueryManager`, and require a
`StoreCacheGuard` (i.e. the owned lock, so we can manually drop it when we feel like so).

I've looked at all the other methods, and they do require the cache for writing into it and the store.
At the limit we could also move `SyncedKeyQueryManager::users_for_key_query`
into `KeyQueryManager`, but the lock in there is hold for a very short-time, so it shouldn't be an issue.

* Add test for the key query deadlock while waiting for the response.

* Update crates/matrix-sdk-crypto/src/machine.rs

Co-authored-by: Richard van der Hoff <1389908+richvdh@users.noreply.github.com>
2023-11-03 11:36:54 +01:00
Richard van der Hoff c2f422209f Review comment: process failed devices together 2023-11-03 10:43:26 +01:00
Richard van der Hoff d90c623375 Add a test for devices with no key map 2023-11-03 10:43:26 +01:00
Richard van der Hoff abd6779210 Inline OlmMachine::receive_keys_claim_response
it's a bit pointless.
2023-11-03 10:43:26 +01:00
Richard van der Hoff d48c27dc86 Remove redundant key_id arg on create_session test helper 2023-11-03 10:43:26 +01:00
Richard van der Hoff 303417eae2 Factor out SessionManager::create_sessions
... and use it in some tests.

Simplify some of the test code by not building a whole keys/claim response.
2023-11-03 10:43:26 +01:00
Richard van der Hoff 017d72e80f Hoist check for missing OTKs to SessionManager
`Account::create_outbound_session` no longer takes an entire list of keys;
rather it takes a single key and it is up to the caller to pick a key out of
the list.

This in turn means that `SessionCreationError` loses one of its reason codes.
2023-11-03 10:43:26 +01:00
Timo d7f6231acd Add perParticipantE2EE to element call url. (#2807)
* Add `perParticipantE2EE` to element call url
* add ffi
* nightly fmt
* refactor to use enum + test
* rename to PerParticipantKeys
* cleanup (spelling + formatting)

Signed-off-by: Timo K <toger5@hotmail.de>
2023-11-03 10:42:50 +01:00
Marco Romano 6a27fc2e04 Upgrade uniffi
This will include https://github.com/mozilla/uniffi-rs/pull/1781
Which fixes https://github.com/mozilla/uniffi-rs/issues/1760
2023-11-03 10:19:20 +01:00
Jonas Platte 3481cde1dc Fix unquoted strings in tracing fields 2023-11-02 17:46:38 +01:00
Jonas Platte d821d611ba sdk: Clean up logging in sliding_sync
- Don't include pos in event data, it's available in a parent span
- Remove superfluous backtick
- Rewrap an `info!` invocation
2023-11-02 17:46:38 +01:00
Jonas Platte 4485abbfdf Replace all two uses of async-std with equivalent tokio functionality 2023-11-02 17:21:48 +01:00
Jonas Platte 2d19c7ad65 crypto: Use Option::map in UsersForKeyQuery::maybe_register_waiting_task 2023-11-02 17:21:48 +01:00
Jonas Platte 1692460b30 Simplify dependency specifications for tokio
matrix-sdk-common and matrix-sdk-crypto were repeating things that would
be inherited from the workspace dependency specification anyways.
2023-11-02 17:21:48 +01:00
Benjamin Bouvier 250d63c6da fix: change the event_type after encrypting
The event_type passed to `encrypt_room_event_raw` must be the one of the cleartext event, not `m.room.encrypted`. The returned event has the expected type.
2023-11-02 16:56:50 +01:00
Jonas Platte 0a33642851 widget: Change content to RawValue representation 2023-11-02 16:56:50 +01:00
Jonas Platte 64c9fa5542 sdk: Allow different raw JSON types for send_raw, send_state_event_raw 2023-11-02 16:56:50 +01:00
Jonas Platte d042bcce04 crypto: Optimize OutboundGroupSession::encrypt 2023-11-02 16:56:50 +01:00
Jonas Platte 7d52322687 crypto: Update raw encryption methods to take &Raw content 2023-11-02 16:56:50 +01:00
Jonas Platte b797635255 crypto: Restrict visibility of internal types
This makes it more obvious that breaking changes to these types don't
break the public API.
2023-11-02 16:56:50 +01:00
Jonas Platte d9845c1658 crypto: Put event type before content for raw encryption methods 2023-11-02 16:56:50 +01:00
Daniel Abramov 4428b525ea widget: Implement limits for requests 2023-11-02 14:56:51 +00:00
Jonas Platte b67ca2c123 ci: Increase log level for coverage job 2023-11-02 12:58:03 +01:00
Jonas Platte 7be84ca71a test: Deduplicate tracing_subscriber initialization
… and set a sensible default log level.
2023-11-02 12:58:03 +01:00
Jonas Platte 9956b56a2c test: Remove unused helpers feature from integration testing crate 2023-11-02 12:58:03 +01:00
Jonas Platte afcc7022a2 Exclude remaining Debug impls from coverage reporting 2023-11-02 12:58:03 +01:00
Benjamin Bouvier 8de33f68f3 integration tests: randomize user names better
In the previous situation, running the tests with `cargo test` would sometimes fail because despite appending the number of milliseconds since
the start of epoch to the user names, some user names would clash across different tests, leading to unexpected results. This fixes it by using
an actual RNG in there, so the names don't ever clash.
2023-11-01 07:57:46 +01:00
Damir Jelić 2efb09907b Add a minimal integration test that sends a message in an encrypted room (#2799) 2023-10-31 19:45:18 +01:00
Jonas Platte 91e7f2f722 sdk: Add changes to send-event API to changelog 2023-10-31 12:46:10 +01:00
Kévin Commaille 7e1eaddf5d Bump tracing in cargo manifest
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-31 11:18:44 +00:00
Jonas Platte 61eb9cea8c sdk: Improve documentation of send, send_raw 2023-10-31 11:13:25 +01:00
Jonas Platte a00c0c1c02 ffi: Fix outdated / misplaced comment 2023-10-31 11:13:25 +01:00
Jonas Platte 3fc1b3adb9 sdk: Update argument order for send_state_event_raw 2023-10-31 11:13:25 +01:00
Jonas Platte 1609a73e99 sdk: Update argument order for send_raw 2023-10-31 11:13:25 +01:00
Jonas Platte 8f108d4064 sdk: Fix tracing span field "encrypted" in send_raw 2023-10-31 11:13:25 +01:00
Jonas Platte 463a02a4ef sdk: Make transaction_id truly optional for send and send_raw
… by removing the parameter and returning a named future with a
builder-style `with_transaction_id` method.
2023-10-31 11:13:25 +01:00
Jonas Platte 8a1506206b sdk: Clean up logging in send_raw 2023-10-31 11:13:25 +01:00
Jonas Platte 4b702da8e8 Make IntoFuture implementations a little easier to read and write 2023-10-31 11:13:25 +01:00
Jonas Platte 18b714116e sdk: Stop re-exporting named futures in regular modules
Instead make futures modules part of the API. It is extremely uncommon
to actually refer to a type that implements `Future` by its name / path,
so it's better if these don't show up in documentation pages that are
already large and somewhat cluttered.
2023-10-31 11:13:25 +01:00
Benjamin Bouvier 380735ab46 crypto: put the KeyQueryManager in the IdentityManager 2023-10-31 10:42:14 +01:00
Benjamin Bouvier 4158d00fac crypto: rename KeyQueryManager::tracked_user_loading_lock to loaded_tracked_users 2023-10-31 10:42:14 +01:00
Benjamin Bouvier 56aff649c5 crypto: introduce a KeyQueryManager 2023-10-31 10:42:14 +01:00
Benjamin Bouvier f945a50d22 Update crates/matrix-sdk-ui/src/timeline/mod.rs 2023-10-30 16:37:03 +01:00
Kévin Commaille 901518d745 Merge compare_receipts and pub_or_priv_receipt into a more generic method
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-30 16:37:03 +01:00
Kévin Commaille 1fe4bff05c Improve docs
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-30 16:37:03 +01:00
Kévin Commaille 3690bdd6a1 ui: Add regression test for clearing read receipts
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-30 16:37:03 +01:00
Kévin Commaille 70eb400d11 ui: Add method to know the position in the timeline of a user read receipt
Even if the event it applies to is not visible

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-30 16:37:03 +01:00
Kévin Commaille 1c602a8919 ui: Clear all read receipts maps
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-30 16:37:03 +01:00
Benjamin Bouvier e4ec8f03eb crypto: remove useless fallibility of StoreTransaction::new 2023-10-30 16:08:06 +01:00
Benjamin Bouvier 597cff5d9d crypto: make it impossible to have two Accounts alive at the same time 2023-10-30 16:08:06 +01:00
Daniel Abramov 7dde4ec1e8 widget: Add reading of message-like events 2023-10-30 14:41:21 +01:00
Daniel Abramov 0f420a61dc widget: Add tests for non-allowed matrix requests 2023-10-30 14:41:21 +01:00
Benjamin Bouvier 9d5e7c59f3 Fix tracing timer API so it works with updated tracing 2023-10-30 14:34:56 +01:00
Benjamin Bouvier 1f534821f6 Bump tracing 2023-10-30 14:34:56 +01:00
Jonas Platte 245c102169 examples: Add required clap feature for example-timeline 2023-10-30 12:26:34 +01:00
Benjamin Bouvier 1da785e6cb fix(crypto): upload device keys *before* sending the keys signature during XSigning bootstrapping
There was a bug previously, that the cross-signing bootstrapping could include a signature for a device key that hadn't been uploaded yet.
This fixes it by returning a third (!) request in `bootstrap_cross_signing`, that must be sent as the second in order, and will upload
keys, if required.

Also tweaks documentation. Fixes #2749.
2023-10-27 16:22:24 +02:00
Benjamin Bouvier f917b32407 crypto: remove useless async in a few functions 2023-10-27 16:22:24 +02:00
Benjamin Bouvier 631ab26d8b Rename some variables in crypto related to cross-signing bootstrapping 2023-10-27 16:22:24 +02:00
Benjamin Bouvier 0f442b4c83 crypto: don't use a transaction when it can be avoided
The transaction is useless since the account is used in read-only mode here.
2023-10-27 16:22:24 +02:00
Richard van der Hoff 0c333ed335 Refactor olm message decryption logic (#2751)
The driver for this is really to make sure that we log a warning whenever there is an error, but tbh IMHO the result is somewhat easier to understand.
2023-10-27 12:57:18 +01:00
Jonas Platte 28c4f2dc14 base: Inline MemoryStore inherent methods into StateStore impl block
They were not used anywhere else and had no reason to exist as inherent
methods.
2023-10-27 13:53:26 +02:00
Jonas Platte 686fc99ebd sdk: Replace dashmap usage by std types 2023-10-27 13:53:26 +02:00
Jonas Platte 113bdbd835 base: Replace dashmap usage by std types 2023-10-27 13:53:26 +02:00
Benjamin Bouvier 1d90dd554c Test some verification getters in the heavyweight verification integration test 2023-10-27 13:03:20 +02:00
Jonas Platte 05a1021724 Use assert_let! instead of assert_matches! with bindings 2023-10-26 17:29:29 +02:00
Jonas Platte 23571d0257 ui: Replace qualified path with use 2023-10-26 17:29:29 +02:00
Benjamin Bouvier c533297efd test: move the mocked endpoints into their own functions
Code-motion only.
2023-10-26 17:10:57 +02:00
Benjamin Bouvier 870faa48d1 tests: add mutual verification test 2023-10-26 15:12:34 +02:00
Jonas Platte a515bc8f03 ffi: Remove tokio::Runtime::block_on usage in async fn 2023-10-25 16:16:58 +02:00
Jonas Platte bc81cc317f ui: Log event type when an event fails to deserialize 2023-10-25 15:15:09 +02:00
Stefan Ceriu 570ef38de3 Remove unused anyhow import 2023-10-24 17:00:08 +02:00
Benjamin Bouvier 468337f026 Update bindings/matrix-sdk-ffi/src/session_verification.rs 2023-10-24 17:00:08 +02:00
Stefan Ceriu 040d095a04 Change the is_verified flag source to device.is_cross_signed_by_owner as theoretically the previous implementation was wrong 2023-10-24 17:00:08 +02:00
Stefan Ceriu fa90269e7e Fixes vector-im/element-x-ios/issues/1868 - Incorrect is_verified flag after successfully running verification flow
- the inner user_identity isn't automatically updated when the flow finishes, needs to be fetched again from encryption
2023-10-24 17:00:08 +02:00
Benjamin Bouvier 93edf7a064 tests: add test for cross-signing bootstrapping and unchecked self-verification 2023-10-24 15:20:15 +02:00
Jonas Platte 79065edabf ui: Test next_event_limit for different pagination strategies 2023-10-24 14:04:07 +02:00
Jonas Platte cd1e3928ad ui: Exclude another Debug impl from coverage reporting 2023-10-24 14:04:07 +02:00
Jonas Platte 61335af40d widget: Add a test for receiving live events 2023-10-24 12:58:39 +02:00
Jonas Platte a0128a94ef base: Log when the same sync response is received twice 2023-10-24 12:58:39 +02:00
Damir Jelić 95ced18edd Implement Default for the CrossSigningKeyExport
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-10-24 11:23:45 +02:00
Damir Jelić cd310d52d4 Add a convenience method to check if we have all cross-signing keys 2023-10-24 11:23:45 +02:00
Damir Jelić dc92ce8b40 Add a convenience method to get your own device out of the store 2023-10-24 11:23:45 +02:00
Daniel Abramov b0aa0ec5de widget: Add unit test to cover possible "deadlock" 2023-10-23 22:31:56 +02:00
Daniel Abramov d81d2bf01a widget: Let machine's process() return Action 2023-10-23 22:31:56 +02:00
Richard van der Hoff 7d2d1a53bf Exclude Store from tracing
Debug output for the `Store` is extremely verbose, so we don't want to log it
every time we touch this function.
2023-10-23 19:50:25 +02:00
Benjamin Bouvier 0107bcfd0d cross-signing bootstrapping: test that failure in bootstrapping doesn't block login 2023-10-23 19:38:30 +02:00
Benjamin Bouvier e1feefcbb0 chore: make clippy happy 2023-10-23 19:38:30 +02:00
Benjamin Bouvier 456472af6c tests: for cross-signing bootstrapping 2023-10-23 19:38:30 +02:00
Benjamin Bouvier 536d1ab527 feat: allow cross-signing bootstrapping in OIDC too 2023-10-23 19:38:30 +02:00
Benjamin Bouvier 0754c75436 client builder: add a method to set encryption settings 2023-10-23 19:38:30 +02:00
Benjamin Bouvier 1c0c9b8ab3 encryption: prefix a few test case names with test_ 2023-10-23 19:38:30 +02:00
Benjamin Bouvier 11f4b54394 encryption: add documentation for the new methods 2023-10-23 19:38:30 +02:00
Damir Jelić 80bbd77f3b feat: allow automatically enabling cross-signing on login 2023-10-23 19:38:30 +02:00
Jonas Platte 6f992d1ad8 ui: Add a test for updating member profiles 2023-10-23 19:34:23 +02:00
Jonas Platte 1ce67cc9e6 test: Use DEFAULT_TEST_ROOM_ID in more places 2023-10-23 19:34:23 +02:00
Jonas Platte 254efed150 test: Rename DEFAULT_SYNC_ROOM_ID to DEFAULT_TEST_ROOM_ID
… and move to matrix-sdk-test's crate root.
2023-10-23 19:34:23 +02:00
Valere f43eb94a8c Better documentation for share_room_key (#2741)
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-10-23 18:37:56 +02:00
Damir Jelić 9ccc36ccc8 Add more tests for the secret storage support in the crypto crate 2023-10-23 18:35:54 +02:00
Benjamin Bouvier 17dead5641 read receipts: avoid allocating an indexmap consumed as an iterator thereafter 2023-10-23 17:51:10 +02:00
Benjamin Bouvier 3ea9b8ed53 Apply suggestions from code review 2023-10-23 17:26:11 +02:00
Kévin Commaille a07062a990 Use std::cmp::Ordering
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Kévin Commaille 7b2a502e7d ui: Add tests for initial user read receipt
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Kévin Commaille 177f15b2e5 ui: Allow to get latest user read receipt for any room data provider
Allows to test the method with unit test

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Kévin Commaille 3bd10a05c1 Add a test for receipt comparison
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Kévin Commaille ff83f5abcb ui: Handle read receipts in the main thread
Improves the compatibility with clients using threads

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Kévin Commaille ddb4bf13b1 ui: Add user_receipt method on RoomDataProvider
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-23 17:26:11 +02:00
Stefan Ceriu 0006f85103 Make reldbg inherit dbg and contain debug symbols 2023-10-23 15:31:22 +02:00
Timo 4933a50496 Add active calls to RoomInfo 2023-10-23 15:05:12 +02:00
Benjamin Bouvier a052f26748 Bump synapse image version in integration testing 2023-10-23 14:56:11 +02:00
Benjamin Bouvier b9b4e4e1e0 test: bump the sliding-sync image versions 2023-10-23 13:55:42 +02:00
Benjamin Bouvier 55257f2b8d test: remove uninteresting smoke test for sliding sync in integration suite 2023-10-23 13:55:42 +02:00
Benjamin Bouvier 6368c699c2 test: Merge the two integration test suites into a single one 2023-10-23 13:55:42 +02:00
Damir Jelić 7440ce0a0c Use the GlobalAccountDataEventType instead of a string for the event type 2023-10-23 10:24:14 +02:00
Damir Jelić fda24d312b Re-export the MacError type of the secret storage module 2023-10-23 10:24:14 +02:00
Richard van der Hoff 21a4e7d1b2 Clean up to-device message logging (#2747) 2023-10-20 17:54:27 +00:00
Richard van der Hoff ed6f658405 Fix spurious "unknown secret request" warning (#2755) 2023-10-20 18:37:17 +01:00
Daniel Abramov 4ea8ecaac8 widget: Add missing unit tests for error cases 2023-10-20 17:52:10 +02:00
Benjamin Bouvier 71627a29a6 style: use not().then() in one place 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 2f90f9d55d account: make it more obvious what Account::clone_internal is, so it can't be misused 2023-10-20 17:04:40 +02:00
Benjamin Bouvier eb5bf4d51f store cache: copy the account back into the cache if there were pending changes to it 2023-10-20 17:04:40 +02:00
Benjamin Bouvier fc35d86a69 test: use unwrap() instead of Result in a test 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 08450da5e1 crypto: move Store::mark_tracked_users_as_up_to_date to the StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 9ecc6ddd1e crypto: move Store::tracked_users to StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 71229dd98b crypto: remove useless parameter in receive_device_changes 2023-10-20 17:04:40 +02:00
Benjamin Bouvier a2702e6d98 crypto: move Store::update_tracked_users to the StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 0feccc7fee crypto: remove useless call to self.cache in Store::users_for_key_query
The store cache can be filled lazily when it's actually needed. It's not needed in this
function here, and may be needed in another function the caller of this function may call
later. No need to preemptively fill the cache here.
2023-10-20 17:04:40 +02:00
Benjamin Bouvier f580966408 crypto: move Store::mark_user_as_changed to the StoreCache too 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 537ac8a269 crypto: fix test failure 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 65f4cc151c crypto: move Store::mark_tracked_users_as_changed to StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 7c51950167 crypto: move Store::ensure_sync_tracked_users to the StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 3030bcbf1e crypto: introduce a RwLock on the StoreCache 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 3d7a56e468 crypto: remove inner mutable shared state from Account 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 8bfcd20801 crypto: make Account not clonable and the Account in the cache optional 2023-10-20 17:04:40 +02:00
Benjamin Bouvier 3570519c0c crypto: make StoreCache::account fallible and async 2023-10-20 17:04:40 +02:00
Benjamin Bouvier a82964ac75 crypto: get rid of test-only OlmMachine::account 2023-10-20 17:04:40 +02:00
Benjamin Bouvier be96ddb24a crypto: introduce StoreCache::account() function 2023-10-20 17:04:40 +02:00
Jonas Platte 6d0ed67679 widget: Rename remaining "permissions" to capabilities
… a few uppercase Permissions were missed last time.
2023-10-20 13:28:53 +02:00
Jonas Platte 3c74809374 widget: Attach room ID to events from sync 2023-10-20 13:00:43 +02:00
Jonas Platte fc6549213b widget: Filter incoming matrix events 2023-10-20 13:00:43 +02:00
Jonas Platte f937e7d14a widget: Fix ResponseData type for notify requests 2023-10-20 13:00:43 +02:00
Daniel Abramov 9d8e5a0d08 widget: Add subscribe / unsubscribe event handling 2023-10-20 13:00:43 +02:00
Daniel Abramov 4900089669 widget: Remove obsolete allow(dead_code) 2023-10-20 13:00:43 +02:00
Jonas Platte a4fd054705 widget: Properly handle matrix driver request errors
Co-authored-by: Daniel Abramov <daniel.abramov@element.io>
2023-10-20 12:05:03 +02:00
Daniel Abramov 685c51b3af widget: Add OpenID request handling 2023-10-20 12:05:03 +02:00
Jonas Platte 70b8ac425f widget: Fix name, response fields of capabilities request 2023-10-20 09:56:36 +02:00
Jonas Platte 7582b4fda9 widget: Fix field name in supported-versions response 2023-10-20 09:56:36 +02:00
Jonas Platte a2d05ed002 widget: Remove unnecessary type wrapping
… and move some types around.
2023-10-20 09:56:36 +02:00
Jonas Platte 489d699cc5 widget: Remove unused Serialize implementation 2023-10-20 09:56:36 +02:00
Jonas Platte 561db87b64 widget: Add integration tests for sending events 2023-10-19 15:25:53 +02:00
Jonas Platte 4982f87c59 widget: Add support for send-event fromWidget requests
Co-authored-by: Daniel Abramov <daniel.abramov@element.io>
2023-10-19 15:25:53 +02:00
Jonas Platte 14cf923361 widget: Add tests for event reading requests 2023-10-19 13:01:57 +02:00
Jonas Platte 3ce7126d58 widget: Add support for read-state-event fromWidget requests
Co-authored-by: Daniel Abramov <daniel.abramov@element.io>
2023-10-19 13:01:57 +02:00
Jonas Platte 946976a561 widget: Add EventFilter::matches_state_event_with_any_state_key 2023-10-19 13:01:57 +02:00
Jonas Platte e58ee3bcf1 widget: Fix copy-paste comment error 2023-10-19 13:01:57 +02:00
Jonas Platte c0985942dd widget: Actually add request ID to process_from_widget_request span 2023-10-19 13:01:57 +02:00
Jonas Platte 3b4108e519 widget: Add Deserialize impl for StateKeySelector 2023-10-19 13:01:57 +02:00
dependabot[bot] 70524e2f5c build(deps): bump rustix from 0.37.24 to 0.37.25
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.37.24 to 0.37.25.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.37.24...v0.37.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-19 11:58:24 +02:00
Jonas Platte d5bda8147f ffi: Remove _blocking functions
Except for full_room_blocking, which is apparently noticably faster.
They were only added as a workaround for a UniFFI bug that has now been
fixed.
2023-10-19 07:56:06 +02:00
Jonas Platte 02a0916bc2 widget: Rename "permissions" to "capabilities"
… to match the postMessage JSON API.
2023-10-18 18:17:31 +02:00
Jonas Platte 01ec91bda4 widget: Rename Capabilities to CapabilitiesState
… and move the None case into it to avoid having to wrap it in Option.
2023-10-18 18:17:31 +02:00
Jonas Platte 12db7516ef widget: Add get_supported_api_versions support
Co-authored-by: Timo K <timok@element.io>
2023-10-18 15:52:00 +00:00
Jonas Platte af71d831c6 Fix 'verifying' typos 2023-10-18 17:50:51 +02:00
Daniel Abramov b623c7c357 widget: Complete ContentLoaded implementation
And cover it with tests.
2023-10-18 16:44:03 +02:00
Mauro Romito 5f0e5c7407 added the with_mentions method 2023-10-18 15:42:14 +02:00
Jonas Platte 2c80307e53 widget: Add content_loaded initialization 2023-10-18 15:03:58 +02:00
Jonas Platte 1aef675a97 widget: Fix a small error in test code 2023-10-18 15:03:58 +02:00
Jonas Platte ea36eaa09a widget: Implement fromWidget response sending
… and send an error response when the request fails to deserialize.
2023-10-18 15:03:58 +02:00
Jonas Platte 9c71c2b733 widget: Rename process_message_from_widget to process_widget_message
… to make it clearer that it's not specifically about fromWidget messages.
2023-10-18 15:03:58 +02:00
Jonas Platte 46a8f4e841 widget: Clean up json! indentation in test 2023-10-18 15:03:58 +02:00
Daniel Abramov eb5f05d8c5 widget: Add missing driver requests 2023-10-18 14:44:46 +02:00
Jonas Platte 29fc3d0289 widget: Shorten tracing instrument args 2023-10-18 13:26:51 +02:00
Daniel Abramov 04fcc0bb87 widget: split parsing of common widget header 2023-10-18 13:26:51 +02:00
Daniel Abramov 77bae6f421 widget: add a note to the error handling 2023-10-18 13:26:51 +02:00
Daniel Abramov a99cb7c30f widget: move things around in function (chore)
So that most important functions (public interface) come first.
2023-10-18 13:26:51 +02:00
Daniel Abramov 6363cd5902 widget: extract capability negotiation into fn
We will need to re-use it when processing the `ContentLoaded` message
from a widget.
2023-10-18 13:26:51 +02:00
Jonas Platte 541c0ef559 widget: Add an integration test for immediate capability negotiation 2023-10-18 12:34:34 +02:00
Jonas Platte 65cc926612 widget: Add more tracing events 2023-10-18 12:34:34 +02:00
Jonas Platte c550fa80a7 widget: Coalesce matrix driver response messages into an enum 2023-10-18 12:34:34 +02:00
Jonas Platte 5113d6e161 widget: Don't alias ruma request type 2023-10-18 12:34:34 +02:00
Jonas Platte f910d10f6b widget: Rename Event -> IncomingMessage 2023-10-18 12:34:34 +02:00
Jonas Platte 29620144fe widget: Split up Machine::process and add more sanity checks 2023-10-18 12:34:34 +02:00
Daniel Abramov deff0a06f5 widget: Implement capability negotiation 2023-10-18 12:34:34 +02:00
Jonas Platte e038ced2c6 widget: Add toWidget & MatrixDriver response handling skeleton 2023-10-18 12:34:34 +02:00
Richard van der Hoff f75b2cd1d0 Documentation on /keys/query parsing functions (#2674)
These functions are a bit confusing, so I wrote some comments. I also factored out
`get_user_signing_key_from_response`, to reduce duplication and (IMHO) improve clarity.
2023-10-17 16:55:26 +01:00
Jonas Platte 7c4046e463 widget: Implement requesting of capabilities w/o init_on_content_load 2023-10-17 16:55:02 +02:00
Jonas Platte a4cc28f94a widget: Rename client module to machine 2023-10-17 16:55:02 +02:00
Jonas Platte de0543c2e2 widget: Rename settings#id to widget_id 2023-10-17 16:55:02 +02:00
Jonas Platte 4a1e4cd279 widget: Rename ClientApi to WidgetMachine 2023-10-17 16:55:02 +02:00
Jonas Platte a812f17ec4 widget: Clarify MatrixDriver's purpose 2023-10-17 16:55:02 +02:00
Jonas Platte 7c958498bf ui: Raise log level for prev_batch wait timeout
… since it can cause rather annoying problems with the SS proxy
if it happens.
2023-10-17 11:02:15 +02:00
Jonas Platte 06e3fc4235 widget: Add a failing (ignored) test for reading room messages 2023-10-17 10:54:51 +02:00
Jonas Platte 07951b88d3 widget: Separate reading of message and state events
… and use the state store for the latter.

Co-authored-by: Timo K <timok@element.io>
2023-10-17 10:54:51 +02:00
Jonas Platte 5775a6e628 widget: Group trait impls in client::outgoing 2023-10-17 10:54:51 +02:00
Jonas Platte 20021abee3 widget: Fix clippy warning 2023-10-17 10:54:51 +02:00
Jonas Platte b3f37459b7 widget: Reduce visibility on widget::matrix types and their methods 2023-10-17 10:54:51 +02:00
Jonas Platte 8496673310 sdk: Move WidgetSettings constructor to top of impl block 2023-10-17 10:54:51 +02:00
Richard van der Hoff 3c9f48dcb5 common: Add a tracing MakeWriter for writing to a JS Logger
Replace the old `tracing_subscriber::layer::Layer` with a `MakeWriter` which
can then be plugged into a `fmt::Subscriber`.
2023-10-16 20:10:41 +02:00
Jonas Platte 7f3fef5d8b ci: Run documentation CI workflow when un-drafting a PR 2023-10-16 15:44:20 +02:00
Timo 7ee872c4bd Fix avatar url and display name in element call link generation
Signed-off-by: Timo K <toger5@hotmail.de>
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-10-16 11:00:32 +02:00
Richard van der Hoff a433d51efc Add static lifetimes to some constants
My compiler is complaining about lack of explicit lifetimes on these consts
2023-10-13 13:59:56 +02:00
Benjamin Bouvier 41a26e30e9 oidc: use sha2 to hash everything oidc-related
Hashes computed by `compute_session_hash` may be stored in the crypto store, and may or may not
be encrypted; make them slightly more secure by using a more secure hash. Also use sha2 for
the logged hashes that are useful for debugging OIDC issues.
2023-10-13 11:06:08 +02:00
Benjamin Bouvier efb16ed5b4 oidc: remove logging of hashed token
This was useful during development of the OIDC feature, but now that it's rather stable,
let's remove it from the traces.
2023-10-13 11:06:08 +02:00
Jonas Platte 735b0d2d24 ffi: Allow MessageType to contain arbitrary msgtype values, plus body
… allows the FFI type MessageLikeEventContent to hold custom msgtypes,
which is useful for notification.
Also allows sending custom msgtypes, as long as no fields other than the
msgtype itself and body are needed.
2023-10-13 10:06:07 +02:00
Jonas Platte 51ee8209bd sdk: Use BTreeSet instead of HashSet in widget tests
… for deterministic / comparable debug formatting.
2023-10-12 18:39:47 +02:00
Jonas Platte bc0e3609d0 sdk: Rewrap strings in widget tests 2023-10-12 18:39:47 +02:00
Jonas Platte a6a4695dd4 ffi: Inline local variables in WidgetSettings conversion 2023-10-12 18:39:47 +02:00
Alfonso Grillo f826999773 feature: send voice message API (#2697)
This PR adds a ffi binding for sending voice messages in the legacy format (before extensible events). It also makes minor changes in the `matrix-sdk` crate to accomodate this change.

* Add send_voice_message api

* Update ruma-events dependency

* Fix attachment info mapping

* Remove unstable dependency in attachment.rs

* Bump ruma-events

* Fix matrix-sdk Cargo.toml

* Fix formatting issues

* Refactor Voice case

* Remove clone from AttachmentInfo

* Remove duplicate code

* Remove unused imports

* Fix formatting issue

* Rename update function
2023-10-12 16:34:09 +00:00
Jonas Platte ba2a5137dc ui: Fix missing thread relation when sending reply to threaded message 2023-10-12 16:02:34 +02:00
Alfonso Grillo eadc7b9052 feat: enable push rules when changing actions (#2708) 2023-10-12 14:41:12 +02:00
Daniel Abramov b98c4772e5 widget: Define outgoing request types
This includes the requests that are sent to the matrix client (i.e.
a request to send a matrix event) as well as requests that are sent to
the widget.

The difference between this and `Action` is that `Action` is rather
"low-level" (i.e. it has a generic `SendToWidget(String)` variant),
whereas this API is a high-level API that we will use internally to
conveniently issue and `await` for outgoing responses.
2023-10-12 12:20:13 +02:00
Kévin Commaille 10393eee8d ui: Require PaginationOptions strategy to be Sync
Otherwise PaginationOption is not Send.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-12 09:44:40 +00:00
Jonas Platte 4e86954008 Upgrade async-compat 2023-10-11 15:21:18 +02:00
Jonas Platte 4b98d24230 Upgrade UniFFI 2023-10-11 15:21:18 +02:00
Jonas Platte d54668305e ci: Run widget tests in coverage job
… the other experimental features were already getting enabled through
matrix-sdk-ui, so don't need to be enabled explicitly.
2023-10-11 15:13:08 +02:00
Daniel Abramov 9cea46c459 widget: add unit tests for Permissions 2023-10-11 15:13:08 +02:00
Daniel Abramov f0521f72d5 widget: add serialize/deserialize for permissions
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-10-11 15:13:08 +02:00
Benjamin Bouvier 77cb1b7f11 oidc: add cross-process lock test 2023-10-11 14:58:52 +02:00
Jonas Platte b8185c0c67 ui: Fix hidden lifetime warnings 2023-10-11 14:42:24 +02:00
Benjamin Bouvier 97844ea8af oidc: remove dead code 2023-10-11 14:18:55 +02:00
Benjamin Bouvier 51b6ecc0b8 oidc: add test for getters 2023-10-11 14:18:55 +02:00
Benjamin Bouvier 2ad567883c oidc: add test for session_token_stream 2023-10-11 14:18:55 +02:00
Benjamin Bouvier f74f2eda76 oidc: remove dead code
This code path could never be taken:

- the only way to set the cross_process_token_refresh_manager is from calling the same function
- this function is guarded against a lock, so it's not reentrant
- this function's only internally called, during `restore_session` or after a successful login:
both will set a session, hence those functions would fail beforehands if another session had
been set earlier.
2023-10-11 14:18:55 +02:00
Benjamin Bouvier 9e8c6e639b oidc: add comments and tarpaulin annotations 2023-10-11 14:18:55 +02:00
Kévin Commaille b14ac1f2b9 Add test for event visibility change
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille 818a1ba992 Log inconsistent state
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille d88437e707 Fix visibility of types
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille dae8c0a37f Update read receipt of prev event if visibility changed
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille d0f74a76d6 ui: Expose read receipts of hidden events on visible events
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille 54bbb05c22 ui: Rename receipt to new_receipt
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille 148c75120c ui: Move maybe_add_implicit_read_receipt as a method of TimelineInnerStateTransaction
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Kévin Commaille aca6210b6b ui: Keep track of read receipts on all events
Not only event items

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-11 12:00:09 +02:00
Benjamin Bouvier 1a56d6c0d8 fix: live-migrate LatestEvent data from the former format to the newer
#2587 introduced a breaking change in the serialized format of `RoomInfo`, changing the `event`
field format in particular, without a migration. As a result, this lead to users state stores
being invalidated for containing incorrect data, and thus being logged out.

This mitigates the issue by making sure that the LatestEvent type is either deserialized as its
newer format (aka `SerializedLatestEvent`) or its older format (aka `SyncTimelineEvent`). This
way, we maintain compatibility with the previous format. We always serialize to the new one, so
we'd only run this migration once.
2023-10-11 09:08:12 +00:00
Daniel Abramov 3567c9dc65 widget: move client into own module and split up 2023-10-11 09:50:58 +02:00
Daniel Abramov d8d136b330 widget: change signature of the ClientApi
Return `Self` and the channel to process incoming actions.
2023-10-11 09:50:58 +02:00
Daniel Abramov 5b92b0ac37 widget: move client into its own module 2023-10-11 09:50:58 +02:00
Damir Jelić c2bb76029a Rename the encrypt_with_iv method so it's clear that it also can decrypt 2023-10-10 18:15:45 +02:00
Damir Jelić 372e0d3d98 Secret storage support
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-10-10 18:15:45 +02:00
Jonas Platte 194c448047 ui: Update send_reply arguments
- `content` is now of type `RoomMessageEventContentWithoutRelation`,
  the relation was previously always overwritten if set
- `add_mentions` is now inferred from `content.mentions`
2023-10-10 17:14:55 +02:00
Richard van der Hoff 63fab7e292 Clean up duplicate/incomplete tracing 2023-10-10 14:28:06 +02:00
Richard van der Hoff 4e4077d6e0 Improve documentation on QrVerificationData (#2689)
I spent a while trying to figure out how all of this works.
2023-10-10 12:06:16 +00:00
Damir Jelić d62cf340c9 Pin reqwests to 0.11.20 2023-10-10 14:00:55 +02:00
Damir Jelić c1d24aea90 Upgrade the deps for the store-encryption and crypto-ffi crates 2023-10-10 14:00:55 +02:00
Damir Jelić 06bb81aaf2 fixup! Add a test ensuring that we didn't mess up our Device deserialization 2023-10-10 14:00:55 +02:00
Damir Jelić 235a8bd5ed Run cargo update for all the things 2023-10-10 14:00:55 +02:00
Damir Jelić 2fce44a16a Add a test ensuring that we didn't mess up our Device deserialization 2023-10-10 14:00:55 +02:00
Damir Jelić 97fe9614e5 Upgrade our pbkdf dependency 2023-10-10 14:00:55 +02:00
Damir Jelić cda0a5da13 Remove atomic from our deps 2023-10-10 14:00:55 +02:00
Jonas Platte 129a5e1acc ui: Wait for pagination token across multiple sync response notifs
… if wait_for_token flag is set for backwards-pagination.
2023-10-10 13:39:59 +02:00
Damir Jelić 04565378fa Only mark our own user identity as verified if it wasn't already 2023-10-10 13:04:09 +02:00
Damir Jelić 2843aef352 Ensure that the private cross-signing keys get persisted after invalidation 2023-10-10 13:04:09 +02:00
Damir Jelić 626df0b589 Test that the invalidated private cross-signing keys get correctly persisted 2023-10-10 13:04:09 +02:00
Damir Jelić 4918710cdc Add the ability to create a test IdentityManager with an arbitrary user ID 2023-10-10 13:04:09 +02:00
Kévin Commaille e674ccf5a4 Use timeline.items() instead of stream
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-10 12:05:57 +02:00
Kévin Commaille d25acdf237 Remove is_empty variable
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-10 12:05:57 +02:00
Kévin Commaille f7bf417996 ui: Test explicit read receipt ignored when implicit read receipt exists
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-10 12:05:57 +02:00
Kévin Commaille e778316918 ui: Do not add explicit receipts if implicit receipt is newer
Should avoid users receipts appearing on two different events in the timeline

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-10 12:05:57 +02:00
Kévin Commaille d03c255342 ui: Create ReadReceipts struct
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-10 12:05:57 +02:00
Jonas Platte a88afd818b ui: Add more tracing events around back-pagination tokens 2023-10-09 17:21:52 +02:00
Daniel Abramov 880e918a5e widget: Add missing license headers 2023-10-09 14:55:58 +00:00
Benjamin Bouvier c64d3b4b7a Introduce a store transaction for Account (#2660)
This adds a new `StoreTransaction` type, that wraps a `StoreCache` and a `Store`. The idea is that it will allow write access to the `Store` (and maintains the cache at the same time), while the `Store::cache` method will only give read-only access to the store's content.

Another new data type is introduced, `PendingChanges`, that reflects `Changes` but for fields that are properly maintained in the `StoreCache` and that one can write in the `StoreTransaction`. In the future, it wouldn't be possible to use `save_pending_changes` from the outside of a `StoreTransaction` context.

The layering is the following:

- `Store` wraps the `DynCryptoStore`, contains a reference to a `StoreCache`.
- When read-only access is sufficient, one can get a handle to the cache with `Store::cache()`.
- When a write happens, then one can create a `StoreTransaction` (later, only one at a time will be allowed, by putting the `StoreCache` behind a `RwLock`; this has been deferred to not make the PR grow too much).
- Any field in the `StoreCache` will get a method to get a reference to the cached thing: it will either load from the DB if not cached, or return the previously cached value. 
- Any field that can be written to will get a method to get a mutable reference in the `StoreTransaction`: it will either load from the cache into a `PendingChanges` scratch pad, or return the scratchpad temporary value.
- When a `StoreTransaction::commit()` happens, fields are backpropagated into the DB *and* the cache. 

Then, this `StoreTransaction` is used to update a `ReadOnlyAccount` in multiple places (and usage of `ReadOnlyAccount` is minimized so as not to require a transaction or cache function call as much as possible). With this, the read-only account only exists transiently, and it's only stored long-term in the cache.

Followup PRs include:

- making the `ReadOnlyAccount` not cloneable
- remove inner mutability from the `ReadOnlyAccount`
- add a `RwLock` on the `StoreTransaction`

Part of https://github.com/matrix-org/matrix-rust-sdk/issues/2624 + https://github.com/matrix-org/matrix-rust-sdk/issues/2000.

---

* crypto: Replace some uses of `ReadOnlyAccount` with `StaticAccountData` and identify tests

* crypto: introduce `StoreTransaction` to modify a `ReadOnlyAccount`

* crypto: introduce `save_pending_changes`, aka `save_changes` v2

* crypto: Start using `StoreTransaction`s to save the account, get rid of `Store::save_account` + `Account::save`

* crypto: use `StoreTransaction` to save an account in `keys_for_upload`

* crypto: use `StoreTransaction` and the cache in more places

* crypto: remove `Account` from the `Changes` \o/

* crypto: remove last (test-only) callers of `Store::account()`

* crypto: move `ReadOnlyAccount` inside the cache only

* crypto: use `ReadOnlyAccount` and `Account` in fewer places

whenever we can use `StaticAccountData` in place.

* crypto: make tests rely less on OlmMachine

* crypto: Don't put the `ReadOnlyAccount` behind a RwLock just yet

+ clippy
2023-10-09 14:16:06 +00:00
Kévin Commaille 41de52b6b0 ui: Reimplement compare_events_positions using all timeline events
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-09 12:13:03 +02:00
Kévin Commaille 155ec45a9e ui: Keep track of the order of all events in the Timeline
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-10-09 12:13:03 +02:00
Ivan Enderlin 487f85b9e3 feat: Make latest_event faster regarding member profile
feat: Make `latest_event` faster regarding member profile
2023-10-09 11:41:32 +02:00
Ivan Enderlin 76ace82298 doc(ui): Fix a typo. 2023-10-09 11:22:43 +02:00
Ivan Enderlin a61bc77572 chore(base): Clean up. 2023-10-09 11:21:56 +02:00
Jonas Platte d216414608 ui: Make loop / continue comments less ambiguous 2023-10-09 11:16:03 +02:00
Jonas Platte 23390403b6 ui: Add more tracing to pagination 2023-10-09 11:16:03 +02:00
Jonas Platte d1ad16fbab ui: Document paginate_backwards_impl 2023-10-09 11:16:03 +02:00
Benjamin Bouvier 2a78b925e4 crypto: move all the Account methods in a single impl block 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 57b1442e1c crypto: rename ReadOnlyAccount to Account \o/ \o/ \o/ \o/ 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 1e9c05ad6f crypto: get rid of Account \o/ \o/ \o/ 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 05d65e9b60 crypto: move methods from Account to ReadOnlyAccount 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 850d25d8ec crypto: tweak a few code comments 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 0b818fe4a5 crypto: use a Store in the DehydratedDevice data structure as it's sufficient 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 03b4e87bd9 crypto: retrieve the StaticAccountData from the store in more places 2023-10-09 11:00:13 +02:00
Benjamin Bouvier d379d755e9 Fix: recreate a rehydrated OlmMachine with the correct DeviceId 2023-10-09 11:00:13 +02:00
Benjamin Bouvier 0013963ea5 crypto: retrieve the UserId/DeviceId from store methods instead of duplicating them 2023-10-09 11:00:13 +02:00
Daniel Abramov 0726955678 widget: minor fixes in formatting, typos, etc 2023-10-09 10:55:48 +02:00
Daniel Abramov 8e501c5d6b widget: rename widget_settings -> settings mod
To stay consistent with the rest of the concise naming "conventions"
within the `widget` module.
2023-10-09 10:55:48 +02:00
Ivan Enderlin 7842611ad9 test: Use the sync_timeline_event! macro once more. 2023-10-09 10:26:08 +02:00
Ivan Enderlin c5b137a83c test(ui): Write test for EventTimelineItem::from_latest_event with cached sender info. 2023-10-09 10:24:23 +02:00
Ivan Enderlin 438c51708d chore: Address feedbacks. 2023-10-09 09:52:29 +02:00
Damir Jelić 6c9cfe1368 Bump our vodozemac version 2023-10-06 16:19:03 +02:00
Jonas Platte d85ab43b81 crypto: Replace dashmap usage in the rest of the crate 2023-10-06 14:42:36 +02:00
Jonas Platte dfabef0036 crypto: Replace dashmap usage by std types in MemoryStore 2023-10-06 14:42:36 +02:00
Jonas Platte 131c3dbf30 crypto: Put both maps in WaitQueue behind one lock 2023-10-06 14:42:36 +02:00
Jonas Platte feba68779b Remove build as parent span of room_update_handler
… but record the relationship between them using follows_from.
2023-10-06 14:01:02 +02:00
Nicolas Mauri 108508e365 Fix: wait for disk operations to finish before returning the media file in get_media_file. (#2672)
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-10-06 09:57:48 +00:00
Jonas Platte b017e49d89 crypto: Detect and fix 'sign'-related typos 2023-10-06 11:38:32 +02:00
Jonas Platte a8158ae6bf crypto: Silence clippy false-positives
Upstream issue: https://github.com/rust-lang/rust-clippy/issues/11383
2023-10-06 10:55:17 +02:00
Jonas Platte a099c7c4a9 sdk: Replace str::to_string with to_owned 2023-10-06 10:55:17 +02:00
Timo 2120acabbb sdk: Add url creation to WidgetSettings
Signed-off-by: Timo K <toger5@hotmail.de>
2023-10-05 19:54:46 +02:00
Jonas Platte 7576a73fcb Upgrade Ruma to latest crates.io release 2023-10-05 17:46:34 +02:00
Benjamin Bouvier d8c2ad7693 ffi: use Timeline::send_single_receipt instead of Room::send_single_receipt for sending read receipts
The latter will always send a query, while the former will attempt to deduplicate sending a read receipt, if the event was already marked as read.
2023-10-05 16:59:28 +02:00
Benjamin Bouvier 1d01c32491 chore: remove unused FFI send_read_marker 2023-10-05 16:59:28 +02:00
Jonas Platte 2315c46a1d sdk: Remove sync gap broadcast channels
They were a stop-gap solution for detecting limited responses and have
been obsolete since the introduction of room update handlers.
2023-10-05 09:29:59 +02:00
Alfonso Grillo aa1b871d00 ffi: Refine NotificationSettingsError type 2023-10-04 15:58:44 +02:00
Jonas Platte 504861a532 sqlite: Upgrade deadpool, rusqlite 2023-10-04 15:43:08 +02:00
Jonas Platte d3902fe375 ci: Upgrade crate-ci/typos 2023-10-04 13:48:31 +02:00
Jonas Platte 749b4df321 ui: Move magic number to a named constant 2023-10-04 13:23:47 +02:00
Jonas Platte 7482246668 test: Test timeline reset while pagination is running 2023-10-04 13:23:47 +02:00
Jonas Platte 2bc0651c94 test: Test back-pagination request deduplication 2023-10-04 13:23:47 +02:00
Jonas Platte 7f934040bc test: Test wait_for_token functionality in back-pagination 2023-10-04 13:23:47 +02:00
Jonas Platte b9c05ca934 test: Move room messages test JSON to the only module that uses it 2023-10-04 13:23:47 +02:00
Jonas Platte 75d64e697e test: Remove unused constants from test_json::messages 2023-10-04 13:23:47 +02:00
Jonas Platte f62d561cb0 ui: Verify token before prepending events from back-pagination 2023-10-04 13:23:47 +02:00
Jonas Platte 6cfa383652 ui: Move BackPaginationStatus definition to pagination module 2023-10-04 13:23:47 +02:00
Jonas Platte 1ab305a9d8 ui: Store back-pagination tokens inside TimelineInnerMetadata 2023-10-04 13:23:47 +02:00
Jonas Platte 0dcfe4f4f5 ui: Fix a typo 2023-10-04 13:23:47 +02:00
Jonas Platte 03c70220a1 ui: Remove forwards pagination token
It's not currently used.
2023-10-04 13:23:47 +02:00
Benjamin Bouvier 0d592e4051 chore: make Client::homeserver not async by changing the underlying kind of mutex
The mutex used for the `homeserver` field is very short-lived, so use a std mutex instead, which
makes a few methods sync instead of async.
2023-10-03 14:25:48 +02:00
Alfonso Grillo 08b3c0e47e Feature: add API for setting underride push rule's actions (#2644) 2023-10-03 10:08:17 +02:00
Benjamin Bouvier 7e8827aec2 fix: make the crypto memory store write the next_batch_token only if provided
This matches the behaviors of the two other implementations.
2023-10-02 19:05:07 +02:00
Benjamin Bouvier 3fa79ce891 chore: make the crypto stores more robust by serializing save_changes()
Stores may race when performing writes, since in `save_changes` some data is pickled, and live across await points (it could be stale after an
await point). To prevent multiple threads racing when calling into `save_changes`, a new lock is introduced there.
2023-10-02 19:05:07 +02:00
Benjamin Bouvier 76c3f2a139 chore: remove a few spurious async on MemoryStore 2023-10-02 19:05:07 +02:00
Benjamin Bouvier 8d6f414375 chore: move CryptoStore::save_account to Store::save_account
as it can be implemented in terms of other methods already present in the `CryptoStore` trait.
2023-10-02 19:05:07 +02:00
Matthew Hodgson 9ddef138c1 feat: support creating emotes (#2648)
* support creating emotes

adds an emote param to message_event_content_from_markdown and
message_event_content_from_html so that clients can create
m.room.message events with msgtype emote (on the assumption
that the msgtype returned in the RoomMessageEventContentWithoutRelation
is immutable, and so can't be overridden by the client)

* lint

* switch to separate methods per review feedback
2023-10-02 18:48:33 +02:00
Benjamin Bouvier 7203ae9576 fix: use insecure OIDC whenever configuring with an http homeserver URL (#2652)
Fixes https://github.com/matrix-org/matrix-rust-sdk/issues/2645.

The previous PR only enabled support for `server_name` homeserver configurations, this makes it work for `homeserver_url` too.
2023-10-02 10:27:30 +00:00
Ivan Enderlin 584db1d54c chore(base): Fix imports and features. 2023-10-02 10:45:08 +02:00
Ivan Enderlin 160f9233fa chore(sdk): Fix imports and features. 2023-10-02 10:44:50 +02:00
Ivan Enderlin ed759cae85 chore(ui): Replace EventTimelineItem with Self. 2023-10-02 10:11:17 +02:00
Ivan Enderlin 2f97301d5c chore(ui): Add homepage and license to Cargo.toml
chore(ui): Add `homepage` and `license` to `Cargo.toml`
2023-10-02 10:07:41 +02:00
Ivan Enderlin ba21e3bb02 feat(ui): Fallback to the slow path if profile is None. 2023-10-02 10:02:03 +02:00
Ivan Enderlin 0a119b55b3 fix(base): Fix migration helper for RoomInfo. 2023-10-02 10:01:48 +02:00
Ivan Enderlin 58998e4bbe feat(ui): Profile implements Default. 2023-10-02 09:49:56 +02:00
Ivan Enderlin 86ed582744 feat(ui): RoomDataProvider has profile_from_latest_event.
This patch renames `RoomDataProvider::profile`
to `::profile_from_user_id`. This patch also adds
`RoomDataProvider::profile_from_latest_event`.
2023-10-02 09:49:56 +02:00
Ivan Enderlin 5c55c2c51f feat(base): Rename LatestEvent::sender_profile to ::has_sender_profile. 2023-10-02 09:49:56 +02:00
Ivan Enderlin c0a3cc478d feat(base): LatestEvent has more methods.
This patch implements `LatestEvent::sender_display_name()`,
`::sender_name_ambiguous()` and `::sender_avatar_url()`.
2023-10-02 09:49:56 +02:00
Ivan Enderlin 83e7d7df11 feat(base): LatestEvent can hold a MinimalRoomMemberEvent.
This patch adds a `sender_profile: Option<MinimalRoomMemberEvent>`
field. Thus, `cache_latest_events` now receives an
`Option<&StateChanges>` and an `Option<&Store>`. The `StateChanges`
is used to read the most recent sender profile, otherwise the sender
profile will be fetch from the storage.
2023-10-02 09:49:56 +02:00
Ivan Enderlin f71a6fc738 feat(base): Latest event is represented by the new LatestEvent type.
This patch adds a new `LatestEvent` type. It wraps the
`SyncTimelineEvent` that was used before. The idea is to add more fields
onto this `LatestEvent` struct, but this patch starts easy by using
`LatestEvent` everywhere where it's required.
2023-10-02 09:49:56 +02:00
Ivan Enderlin 9027607347 doc(ui): Format doc. 2023-10-02 09:49:56 +02:00
Ivan Enderlin 84b1b741a3 feat(ui): all_rooms requires m.room.member: $LAZY.
This patch updates `all_rooms` to require the `m.room.member` state set
to `$LAZY`. That way, it's more likely to get the `m.room.member` event
associated to the latest event without needing to sync all the members.
2023-10-02 09:49:56 +02:00
Ivan Enderlin f7c008af3c chore(ui): Add repository and license to Cargo.toml. 2023-10-02 09:41:29 +02:00
kegsay 58e15f812d oidc: allow http scheme during discovery (#2642)
This is to enable entirely local stacks of Element X to work correctly. It's mostly seamless (no ffi changes) because it ties into `ClientBuilder.insecure_server_name_no_tls()`.

---------

Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-09-29 11:29:30 +00:00
Benjamin Bouvier 9cc9221808 notifications: match decryption errors more precisely when waiting for decryption in the background 2023-09-29 12:37:16 +02:00
Benjamin Bouvier e679182fb5 fix(notifications): don't hard-fail if decrypting failed
Before this commit, a call to get_notification would fail if decrypting failed on the first attempt, because `Room::decrypt_event` will hard-fail if the key wasn't found.
2023-09-29 12:37:16 +02:00
Benjamin Bouvier 787a85615c chore(notifications): tweak logs in notification client 2023-09-29 12:37:16 +02:00
Benjamin Bouvier 6a5a4db5fc fix(notifications): add a mutex to serialize e2ee encryption requests 2023-09-29 12:37:16 +02:00
Benjamin Bouvier 1cedd1097a chore(notifications): rename sliding_sync_mutex to notification_sync_mutex 2023-09-29 12:37:16 +02:00
Benjamin Bouvier 2677d16f2e chore: look ma i'm a 10x engineer ok 2023-09-29 12:25:52 +02:00
Benjamin Bouvier 1e5e13bb19 chore: make use of StaticAccountData in one extra location 2023-09-29 12:25:52 +02:00
Benjamin Bouvier 1eeee288b9 chore: move unsigned_device_keys to StaticAccountData too 2023-09-29 12:25:52 +02:00
Benjamin Bouvier e4fd8e7ac6 chore: move methods from ReadOnlyAccount to StaticAccountData 2023-09-29 12:25:52 +02:00
Valere ab2f18df3b Add new API to request missing secrets (#2641) 2023-09-29 12:12:34 +02:00
Benjamin Bouvier a1f6e2fb16 chore: clippy + remove spurious Arc 2023-09-28 17:21:53 +02:00
Benjamin Bouvier 8f541c9a09 chore: remove ReadOnlyAccount from the Account
And use the `self.store.account()` when we really need the `ReadOnlyAccount`. Also cache the immutable account data in this Account.
2023-09-28 17:21:53 +02:00
Benjamin Bouvier c01d2d990c chore: use StaticAccountData in more places
Sorry, this commit is a bit big. What happened is that I've pulled a thread: put a `StaticAccountData` there, look at caller; it seems to use only a
static account too, so keep up.

The only contender was the `OwnUserIdentity` data structure which really wants to sign things. Lucky for us, we could pass it a `ReadOnlyAccount`
from a store, in those cases, since we always had a `Store` hanging around; in the future it'll read it from the store cache, which is somewhat
identical.
2023-09-28 17:21:53 +02:00
Benjamin Bouvier 13be4b0dce chore: use a StaticAccountData in the Sas data structures 2023-09-28 17:21:53 +02:00
Benjamin Bouvier 650d99a875 chore: rename account_info/get_account_info to static_account/get_static_account 2023-09-28 17:21:53 +02:00
Benjamin Bouvier 1c311555ef chore: put the immutable parts of ReadOnlyAccount into its own data struct 2023-09-28 17:21:53 +02:00
Kévin Commaille f115bd0e25 base: Fix typo
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille 4595e2c064 indexeddb: Close DB less often during migrations
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille f3b8bdbe1e indexeddb: Migrate RoomInfo to new format
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille 23b34bfc2b indexeddb: Make sure each migration is discrete
We might need to rely on the data in the DB to already be corrected
before using it for another migration

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille d4f0f7a704 sqlite: Migrate RoomInfo to new format
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille 0856cf10fe base: Add migration helpers for converting serialized format of RoomInfo
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille cd4bc7a62c base: Return m.room.create event's content even if it is redacted
Starting with room version 11, all fields are kept when the event is redacted

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Kévin Commaille acfb999e76 base: Make BaseRoomInfo compatible with room version 11
By copying the sender field as the creator field of m.room.create's event content

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-28 16:54:03 +02:00
Benjamin Bouvier 79f408b511 feat: automatically reload the tracked users when getting the cache 2023-09-28 16:37:52 +02:00
Benjamin Bouvier c18ac12ce9 Apply suggestions from code review
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-09-28 16:37:52 +02:00
Benjamin Bouvier 216b3b9d77 feat: add a dummy StoreCacheGuard too, and make use of it 2023-09-28 16:37:52 +02:00
Benjamin Bouvier 00d6b2d2ae chore: rename Store::load_tracked_users to Store::ensure_sync_tracked_users 2023-09-28 16:37:52 +02:00
Benjamin Bouvier 2499d13839 chore: remove outdated code comment 2023-09-28 16:37:52 +02:00
Benjamin Bouvier 074bcca02e feat: introduce dummy crypto cache for the store 2023-09-28 16:37:52 +02:00
Benjamin Bouvier 851b3f2982 chore(crypto): simplify Store::load_tracked_users 2023-09-28 16:37:52 +02:00
Daniel Abramov 585508f461 sdk(widget): Add the matrix (i/o) part 2023-09-28 16:01:26 +02:00
Hubert Chathi 4478826a2f Fix a typo 2023-09-28 15:31:25 +02:00
Marco Romano 0158422dcd Bump ruma
- Bumps ruma to include https://github.com/ruma/ruma/pull/1665
- Enables ruma `compat-arbitrary-length-ids` flag instead of using the now deprecated `lax-id-validation`
2023-09-28 14:01:18 +02:00
Damir Jelić 1f695bf26e Move most of our locks in the Client into the ClientLocks struct 2023-09-28 13:06:09 +02:00
Damir Jelić e37cfe6036 Don't use a static for the mark_room_as_dm lock
Using a static lock would prevent two Client instances to call the
method at the same time.

We're going to assume that people won't have multiple Client instances
for the same user, in which case a static lock would have been helpful.

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-09-28 13:06:09 +02:00
Richard van der Hoff bcbb7c61f8 Enable the tests for encrypted indexeddb stores
Empirically, these seem to work for me, so let's re-enable them
2023-09-28 12:53:40 +02:00
Valere 4c432a66b4 Distinguish between changed and unchanged user identities (#2618) 2023-09-28 10:14:09 +02:00
Damir Jelić 24a203a126 Ensure that we can't try to mark two rooms as a DM at the same time 2023-09-27 16:24:57 +02:00
Jonas Platte cbb7b24cb5 Rewrite public widget API 2023-09-27 14:45:33 +02:00
Jonas Platte ae524b3fa6 ffi: Use FFI's tokio runtime explicitly for acquire_permissions 2023-09-27 13:13:27 +02:00
Richard van der Hoff 563072e7f8 Change the way we store gossip requests in indexeddb (#2626)
Instead of using three separate object stores, use a single one with some structured objects and indexes.

Fixes #2605 (or at least, should make whatever is going wrong much more obvious).

Consists of a series of commits which should be reviewable on their own.
2023-09-27 11:43:07 +01:00
Richard van der Hoff e3d9408d0a matrix-sdk-common: add js console tracing layer (#2620)
Implement a `tracing_subsciber` `Layer` which sends output to the javascript
console.

Obviously, this only works on the wasm32 target.

This is lifted from `matrix-rust-sdk-crypto-wasm`, so that we can use it in
tests etc for other crates.
2023-09-27 11:42:33 +01:00
Jonas Platte b35d40d111 test: Remove unused sync events from test_json 2023-09-27 10:06:32 +02:00
Jonas Platte 3196ac53b2 test: Remove TimelineTestEvent 2023-09-27 10:06:32 +02:00
Jonas Platte 145c5078f2 test: Remove example from SyncResponseBuilder docs
It is only an internal utility, we can copy-paste from other tests.
2023-09-27 10:06:32 +02:00
Jonas Platte 62ecff3e1d test: Remove outdated documentation 2023-09-27 10:06:32 +02:00
Richard van der Hoff fcd593b0bf Improve documentation on xtask ci {wasm,wasm-pack} 2023-09-26 21:30:40 +02:00
Richard van der Hoff 299256fe50 Fix IndexeddbCryptoStore::get_outgoing_secret_requests
Fix a bug which caused `get_outgoing_secret_requests` to return no data.

This is exposed as a public function, and when called that way it does *not*
expect the key to be escaped and encrypted.
2023-09-26 17:40:16 +02:00
Jonas Platte 956d5ef7e7 ffi: Update Room::edit to not make unnecessary network requests 2023-09-26 15:29:23 +02:00
Jonas Platte 2e27142336 ui: Add highlevel API for editing 2023-09-26 15:29:23 +02:00
Jonas Platte 9aa1dd725d test: Use EventBuilder for timeline edit integration test 2023-09-26 15:29:23 +02:00
Jonas Platte 6a69543f64 test: Move timeline edit integration test into new module 2023-09-26 15:29:23 +02:00
Jonas Platte 88194b6828 ui: Remove outdated documentation 2023-09-26 15:29:23 +02:00
Daniel Abramov a1d67206b6 sdk(widget): temporarily supress dead code warning 2023-09-26 13:19:38 +02:00
Daniel Abramov d017bab144 sdk(widget): add sans-io client api impl skeleton 2023-09-26 13:19:38 +02:00
Benjamin Bouvier 1180c6aef9 feat: add integration test for fetch_members causing UTDs 🧪 2023-09-26 12:33:41 +02:00
Benjamin Bouvier 43723c88ec feat: add unit tests 🧪 2023-09-26 12:33:41 +02:00
Benjamin Bouvier cb14813f84 chore: address review comments 2023-09-26 12:33:41 +02:00
Benjamin Bouvier e9362f75f2 chore: fix test that was affected by the issue
An encryption state request was failing, but before the patch, subsequent
requests would end up in success, while this wasn't the case. The new failure
introduced by this patch was a real one, because the encryption state was
not mocked as part of this test (which tries to send a message to a homeserver).
2023-09-26 12:33:41 +02:00
Benjamin Bouvier 502ecfa636 chore: get rid of the outer pinned box, weeeee 2023-09-26 12:33:41 +02:00
Benjamin Bouvier 928be57666 chore: polish DeduplicatedRequestHandler API 2023-09-26 12:33:41 +02:00
Benjamin Bouvier f5521e9e1c feat/fix: also correctly deduplicate preshare_room_key 2023-09-26 12:33:41 +02:00
Benjamin Bouvier 3aa5d12ab7 feat/fix: implement DeduplicatedRequestHandler + use it for encryption_state_request 2023-09-26 12:33:41 +02:00
Benjamin Bouvier e9d6351471 fix: also check the result of base::Client::receive_members 2023-09-26 12:33:41 +02:00
Benjamin Bouvier 120ac7150c reformat: no else after return 2023-09-26 12:33:41 +02:00
Benjamin Bouvier 2a235d8ea6 fix: signal that a concurrent request failed to the caller, instead of assuming it succeeded 2023-09-26 12:33:41 +02:00
Benjamin Bouvier a08327907c fix: don't cause that any /members request following a failed one result in immediate success 2023-09-26 12:33:41 +02:00
Jonas Platte a3799190d4 ui: Remove sender information from logs where it's not relevant 2023-09-26 11:26:25 +02:00
Jonas Platte e8528926be ui: Move debug-logging code out of main event handler logic 2023-09-26 11:26:25 +02:00
Alfonso Grillo b248aa9862 ffi: Add push notification support for unstable m.poll.start 2023-09-26 10:38:24 +02:00
Jonas Platte f47b7f02cc Use as_variant macro in more cases 2023-09-26 10:14:04 +02:00
Jonas Platte 6e15e34700 Use bool::then where applicable 2023-09-26 10:14:04 +02:00
Jonas Platte 1f08e22d0d crypto: Separate module declarations and reexports from imports 2023-09-26 10:14:04 +02:00
Jonas Platte f2c569440e ui: Remove transaction ID parameter on Timeline methods
There is no reason for it to be configurable in the high-level API,
since the timeline manages local echoes automatically.
2023-09-25 16:50:59 +02:00
Jonas Platte 753793c451 ui: Add a test for redacting a TimelineItemContent::MembershipChange 2023-09-25 14:27:50 +02:00
Jonas Platte c791fd6f1d ui: Move m.room.message specific content types into new module
`content.rs` was approaching 1K lines, which is a lot for a single file.
2023-09-25 14:27:50 +02:00
Jonas Platte 8e2ef9b2d0 ui: Move reaction types out of event_item::content
… into their own submodule.
2023-09-25 14:27:50 +02:00
dependabot[bot] 0ae6f740b9 chore(deps): bump aes-gcm from 0.10.2 to 0.10.3
Bumps [aes-gcm](https://github.com/RustCrypto/AEADs) from 0.10.2 to 0.10.3.
- [Commits](https://github.com/RustCrypto/AEADs/compare/aes-gcm-v0.10.2...aes-gcm-v0.10.3)

---
updated-dependencies:
- dependency-name: aes-gcm
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-25 11:38:08 +02:00
Jonas Platte 7213cfe152 ui: Add a test for Timeline::send_reply 2023-09-25 11:26:51 +02:00
Jonas Platte cb8c71ad68 ffi: Add EventTimelineItem::can_be_replied_to 2023-09-25 11:26:51 +02:00
Jonas Platte 579f59c54b ffi: Update send_reply to not make unnecessary network requests
… and add Room::get_event_timeline_item_by_event_id so send_reply can be
called more easily by EX-iOS given its code structure.
2023-09-25 11:26:51 +02:00
Jonas Platte 7430201f56 ui: Add highlevel API for replying 2023-09-25 11:26:51 +02:00
Jonas Platte dd7b3ab7d7 ui: Add timeline::error module 2023-09-25 11:26:51 +02:00
Benjamin Bouvier e8624706d4 logs(sliding sync): don't accumulate room ids when computing limited 2023-09-22 12:05:58 +02:00
Kévin Commaille d4a800aadc base: Small test code fix
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-22 10:54:09 +02:00
Kévin Commaille 9b732e1895 base: Ensure room name is not empty
Ruma used to handle this during deserialization when the field was
an Option.

Especially meaningful when computing the room name, where
an empty string means the name is not set.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-09-22 10:54:09 +02:00
Jonas Platte ff7aa63c5c ui: Ignore flaky test 2023-09-21 19:12:01 +02:00
Jonas Platte 454304a20d Enable eyeball's tracing feature 2023-09-21 19:12:01 +02:00
Marco Romano bd18c373fe Workaround uniffi kotlin bug with empty structs
Adds a dummy fuield to `UnstableVoiceContent`, otherwise uniffi
will generate an empty Kotlin data class which breaks compilation
(Kotlin data classes must have at least 1 field).
Upstream bug: https://github.com/mozilla/uniffi-rs/issues/1760
2023-09-21 14:28:55 +00:00
Jonas Platte fd822fc683 testing: Move last event creation code from TestTimeline to EventBuilder 2023-09-21 16:00:11 +02:00
Jonas Platte 5cf3c1e731 testing: Use EventBuilder in timeline teply integration tests 2023-09-21 16:00:11 +02:00
Jonas Platte 37912a1909 testing: Update EventBuilder method names for clarity 2023-09-21 16:00:11 +02:00
Jonas Platte c89b38b19f testing: Update EventBuilder::make_message_event_with_id argument order 2023-09-21 16:00:11 +02:00
Jonas Platte 2376f16214 testing: Change make_message_event_with_id to take event_id by reference 2023-09-21 16:00:11 +02:00
Jonas Platte 5d5f5e18de testing: Move ALICE, BOB, CAROL statics to test crate 2023-09-21 16:00:11 +02:00
Jonas Platte a5a9940ad9 testing: Extract EventBuilder out of TestTimeline 2023-09-21 16:00:11 +02:00
Jonas Platte fe2e60c60d testing: Replace TimelineTestEvent::Custom with sync_timeline_event! 2023-09-21 16:00:11 +02:00
Jonas Platte 054d26774c testing: Generalize add_timeline_event for sync response room builders 2023-09-21 16:00:11 +02:00
Jonas Platte c8a4bc799b testing: Add sync_timeline_event macro for more type safety in tests 2023-09-21 16:00:11 +02:00
Jonas Platte 82f793ec0f testing: Rename event_builder module to sync_builder
It used to contain a type named EventBuilder, but that has been renamed
to SyncResponseBuilder a while ago.
2023-09-21 16:00:11 +02:00
Ivan Enderlin cce8698e6e Merge pull request #2597 from Hywan/fix-base-poll-unstable-ruma-feature 2023-09-21 14:00:55 +02:00
Ivan Enderlin b495988424 fix(cargo): Move the unstable-msc3381 feature to matrix-sdk-base. 2023-09-21 13:19:10 +02:00
Ivan Enderlin 77d6c3144e Merge pull request #2581 from Hywan/feat-ffi-room-avatar-image-info 2023-09-21 12:24:02 +02:00
Ivan Enderlin 9f4b2bec05 fix(cargo): Enable unstable-msc3381 on ruma.
Without this feature:

```sh
$ cargo check -p matrix-sdk --features experimental-sliding-sync
    Checking matrix-sdk-base v0.6.1 (/Users/hwhost/Development/Element/matrix-rust-sdk/crates/matrix-sdk-base)
error[E0433]: failed to resolve: could not find `poll` in `events`
 --> crates/matrix-sdk-base/src/latest_event.rs:7:5
  |
7 |     poll::unstable_start::SyncUnstablePollStartEvent, room::message::SyncRoomMessageEvent,
  |     ^^^^ could not find `poll` in `events`

error[E0599]: no variant or associated item named `UnstablePollStart` found for enum `AnySyncMessageLikeEvent` in the current scope
  --> crates/matrix-sdk-base/src/latest_event.rs:40:68
   |
40 |         AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(poll)) => {
   |                                                                    ^^^^^^^^^^^^^^^^^ variant or associated item not found in `AnySyncMessageLikeEvent`
```

With the feature, everything goes well.
2023-09-21 11:41:02 +02:00
Ivan Enderlin 2ef05eb464 feat(sdk): Remove SlidingSyncList::room_list_filtered_stream
feat(sdk): Remove `SlidingSyncList::room_list_filtered_stream`
2023-09-21 10:31:20 +02:00
Ivan Enderlin 0989dd7fb7 feat(ui): Add the none filter
feat(ui): Add the `none` filter
2023-09-21 10:20:07 +02:00
Ivan Enderlin 3d8bdba268 feat(sdk): Remove SlidingSyncList::room_list_filtered_stream.
This patch removes `SlidingSyncList::room_list_filtered_stream.
Of course, the only place where it was used,
`RoomList::entries_with_dynamic_adapters` now use `.filter` from
`VectorSubscriberExt`. It's basically less code.
2023-09-21 10:17:06 +02:00
Ivan Enderlin 424135b831 fix(ffi): RoomListItem::full_room is now async
fix(ffi): `RoomListItem::full_room` is now `async`
2023-09-21 09:55:24 +02:00
Ivan Enderlin 7fc77a2d7e fix(ffi): RoomListItem::full_room is now async.
Because it creates a bug on iOS when using `RUNTIME.block_on` here.

This patch also adds `full_room_blocking` temporarily for Kotlin.
2023-09-21 09:35:16 +02:00
Ivan Enderlin ce2bac6818 feat(ffi): Add RoomListEntriesDynamicFilterKind::None. 2023-09-21 09:32:12 +02:00
Ivan Enderlin ef8c82113d test(ui): Test the none filter in real life. 2023-09-21 09:32:12 +02:00
Ivan Enderlin e91f170589 feat(ui): Add the none filter.
It's the opposite of the `all` filter. This one rejects all entries.
2023-09-21 09:32:12 +02:00
Ivan Enderlin 95e8f49afc test(ui): Test an empty pattern for filters. 2023-09-21 09:32:12 +02:00
Jonas Platte 1a15802201 Upgrade Ruma 2023-09-20 14:19:06 +02:00
Jonas Platte 784171e261 ffi: Don't check original sender of message when editing
The timeline checks this already. The network request made for this was
wasteful and unnecessary.
2023-09-20 11:57:38 +02:00
Marco Romano 9a0cc58e4a Support polls as latest event of a room 2023-09-20 09:45:25 +00:00
Ivan Enderlin 8217d18117 doc(ffi): Document media_info of Room::upload_avatar. 2023-09-20 10:32:47 +02:00
Ivan Enderlin a23d497d5c feat(ffi): Room::upload_avatar now has an Option<ImageInfo>.
This patch adds `Option<ImageInfo>` as an argument of
`Room::upload_avatar`.

To achieve that, this patch implements
`TryFrom<ImageInfo> for ruma::events::room::avatar::ImageInfo`.
2023-09-20 10:25:40 +02:00
Ivan Enderlin dfdc32f89a feat(ffi): Rename TimelineError to MediaInfoError.
The `TimelineError` type only contains error variants used for
`ImageInfo`, `AudioInfo`, `VideoInfo`, `FileInfo` and so on. It's never
used for something strictly related to the `Timeline`.

This patch then renames `TimelineError` to `MediaInfoError`.
The `MissingMediaInfoField` variant becomes `MediaField`. The
`InvalidMediaInfoField` becomes `InvalidField`.
2023-09-20 10:00:40 +02:00
Nicolas Mauri 31cb34f909 ffi: Add support for voice messages sent as m.room.message 2023-09-19 18:11:52 +00:00
Jonas Platte a37c487763 examples: Remove nonsensical room state check
Stripped state events are only received for rooms in invite state.
2023-09-19 17:57:39 +02:00
Damir Jelić eeb27adad2 Use the extracted ciphers for the file-based key export support 2023-09-19 15:23:46 +02:00
Damir Jelić 0c726d521b Add a new ciphers module for AES-CTR-256 with HMAC-SHA256
This module is mainly extracting logic we're already using for the
file-based key exporting.

Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-09-19 15:23:46 +02:00
Jonas Platte 13c9b0f803 ffi: Add _blocking versions of async methods in notification_settings 2023-09-19 12:13:42 +02:00
Jonas Platte 40f701986c ffi: Add _blocking versions of async methods in session_verification 2023-09-19 12:13:42 +02:00
Jonas Platte 7f2f3053f1 ffi: Add _blocking versions of async methods in sync_service 2023-09-19 12:13:42 +02:00
Jonas Platte 0ceb93a410 ffi: Add _blocking versions of async methods in room 2023-09-19 12:13:42 +02:00
Jonas Platte 52ce2eed23 Upgrade eyeball, eyeball-im-util
Pulls in an important bug fix for the Limit adapter.
2023-09-19 11:35:18 +02:00
Ivan Enderlin 1e80aae780 feat(ui): RoomList dynamic entries simplifications
feat(ui): `RoomList` dynamic entries simplifications
2023-09-19 11:00:35 +02:00
Ivan Enderlin af7cf3148b test(ui): Ensure that it's fine to reset_to_one_page multiple times. 2023-09-19 10:40:02 +02:00
Ivan Enderlin d0da4ae159 feat(ui): Update the limit if it's different.
Use `SharedObservable::set_if_not_eq` instead of `::set`, so that it
doesn't update the limit to the same value, which would be unnecesary in
this case.
2023-09-19 10:34:27 +02:00
Ivan Enderlin c2166c50b1 chore(ui): Remove a useless clone.
It was necessary before 0f7e5ba4b0, but it
isn't anymore.
2023-09-19 10:33:19 +02:00
Ivan Enderlin a1a5b85fd8 feat(ui): Remove one Mutex.
This patch removes the `Mutex` around `Subscriber<Option<u32>>` inside
the `RoomListDynamicEntriesController`. The `Mutex` was necessary to
get a mutable reference, so that `Subscriber::next_now` could have been
used. However, it's not necessary to use `new_now` in this particular
context. We can use `get` instead, which take an immutable reference,
thus removing the need for the `Mutex`.

A `Mutex` has a non-negligeable cost. This function can be used in a
critical hot path, and must as fast as possible.
2023-09-19 10:29:49 +02:00
Ivan Enderlin 12316ad281 feat(ui): Improve the InputCannotBeApplied displaying
feat(ui): Improve the `InputCannotBeApplied` displaying
2023-09-19 08:56:55 +02:00
Jonas Platte 97c5acff7c Use new functionality from eyeball-im-util 0.5 2023-09-18 19:56:06 +02:00
Jonas Platte 0504eafc0a Upgrade most dependencies 2023-09-18 19:56:06 +02:00
Jonas Platte beeeec34f7 sdk: Wait on sync beat asynchronously 2023-09-18 19:56:06 +02:00
Jonas Platte 45b7e075c9 Remove unused dependencies 2023-09-18 19:56:06 +02:00
Benjamin Bouvier 0ecdc2f43c fix(e2ee): query keys for untracked users even if we didn't explicitly sync members ourselves 2023-09-18 17:24:21 +02:00
Ivan Enderlin ae27164bb3 feat(ui): Improve the InputCannotBeApplied displaying. 2023-09-18 17:02:41 +02:00
Ivan Enderlin e5ba445c56 feat(sqlite) chunk_large_query_over doesn't chunk if not necessary
feat(sqlite) `chunk_large_query_over` doesn't chunk if not necessary
2023-09-18 16:33:52 +02:00
Ivan Enderlin 1f98159213 chore(sqlite): Simplify Vec capacity calculation.
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-09-18 16:20:05 +02:00
Ivan Enderlin 4c244c6d83 feat(sqlite) chunk_large_query_over doesn't chunk if not necessary.
This patch updates `chunk_large_query_over`. This function works great
when it's required to chunk some data over a query. However, if the
data don't need to be chunked, it is possible to get a quicker path that
doesn't involve 2 `Vec` allocations, nor a `split_off` of one `Vec`, nor
a `Vec::extend` (which move data in memory). The quicker path, which is
also the most likely to be hit, simply does a single `Vec` allocation,
and that's it.
2023-09-18 15:13:45 +02:00
Benjamin Bouvier 284bb9702b example(oidc): add automatic persist on session update 2023-09-18 14:34:10 +02:00
Benjamin Bouvier bed0faa143 example(oidc): add sync service integration
And allow to run with an insecure server + auto-refresh token + properly restore session using homeserver discovery
2023-09-18 14:34:10 +02:00
Benjamin Bouvier 5ff83c00c5 Add tests for OIDC (#2558)
* chore(oidc): put impl of OIDC server behind a trait and add tests for the OIDC flow

chore(oidc): add first tests for login/AuthorizationResponse

chore(oidc): add tests for finish_authorization/finish_login/refresh_access_token

chore(oidc): add test for logout

chore(🤷): clippy/fmt

* chore(oidc): move account management test to the new `tests` module

* chore(oidc): address review comments
2023-09-18 14:33:35 +02:00
Damir Jelić f44ebf1bf9 Merge pull request #2553 from matrix-org/poljar/fix-mark-dm-as-room
Add method to fetch account data and fetch the m.direct account data from the server when marking rooms as DMs
2023-09-18 11:19:48 +02:00
Ivan Enderlin 7198239ea6 feat(ui): RoomListService::sync_indicator takes delays as parameters
feat(ui): `RoomListService::sync_indicator` takes delays as parameters
2023-09-18 11:06:24 +02:00
Damir Jelić 790944f216 Update crates/matrix-sdk/src/account.rs
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2023-09-18 11:04:40 +02:00
Marco Romano 3bad812ff8 Use new poll event replacement types
* Bump ruma
* Use new ruma types for UnstablePollStartEventContent
* Use from/into for UnstablePollStartEventContent.
2023-09-18 11:03:41 +02:00
Ivan Enderlin 5e28257d77 feat(ui): RoomListService::sync_indicator takes delays as parameters.
Prior to this patch, we were using 2 constants to define the
sync indicator delays: `SYNC_INDICATOR_DELAY_BEFORE_SHOWING` and
`SYNC_INDICATOR_DELAY_BEFORE_HIDING`. After some discussions with
some users, it appears that it's desirable to make these values
parameterizable.

Thus, this patch updates `RoomListService::sync_indicator` to accept 2
parameters: `delay_before_showing` and `delay_before_hiding`. The patch
also updates the FFI bindings.
2023-09-18 10:44:03 +02:00
Ivan Enderlin 463b207422 feat(ui): Use an initial limit in room list dynamic entries
feat(ui): Use an initial limit in room list dynamic entries
2023-09-18 10:42:28 +02:00
Ivan Enderlin 442e6ca8c0 fix(ui): The invites sliding sync is no longer cached
fix(ui): The `invites` sliding sync is no longer cached
2023-09-18 10:42:00 +02:00
Ivan Enderlin 79231f940f feat(ffi): Improve performances of Room::members
feat(ffi): Improve performances of `Room::members`
2023-09-18 10:40:48 +02:00
Ivan Enderlin 0f7e5ba4b0 feat(ui): Use an initial limit in room list dynamic entries.
In `RoomList::entries_with_dynamic_adapters`, we were using the
`Limit::dynamic` constructor. It builds a `Limit` stream adapter where
the limits come from a `Stream`. The immediate impact is that this
constructor does only return a new `Stream`, but it cannot return
the initial items of the vector. Thus, it wasn't possible to reset
the final `Stream` with a `VectorDiff::Reset { values }`. Instead, we
were emitting a `Clear` (from this code) then a `Append` (from the
`Limit` stream). Sadly, for some client apps, like Element X iOS, these
2 diffs (`Clear` and `Append`) result in a “rendering blink”.

Hopefully for us, now there is new constructors for `Limit`! One of them
is `Limit::dynamic_with_initial_limit`, which returns the initial items
along with the new `Stream`. That's perfect, we can reset the final
`Stream` with `Reset { values }` again, thus removing the “UI blinking”
effect in Element X iOS.

This patch switches `Limit::dynamic` to
`Limit::dynamic_with_initial_limit`, and updates/simplifies the tests
accordingly.
2023-09-18 10:21:51 +02:00
Ivan Enderlin b7240b898c fix(ci): Change tarpaulin output format from Xml to xml
fix(ci): Change tarpaulin output format from `Xml` to `xml`
2023-09-18 10:21:15 +02:00
Ivan Enderlin 17e1e81023 fix(ui): The invites sliding sync is no longer cached.
`RoomListService::new_internal` were creating the `invites` sliding
sync list, with a cache. It is not a good idea. It should not be cached.
Let's remove that :-).
2023-09-18 10:06:37 +02:00
Ivan Enderlin 005d2638cb fix(ci): Change tarpaulin output format from Xml to xml.
It's better huh?
2023-09-18 09:58:09 +02:00
Ivan Enderlin af402b3864 fix(ui): Set RoomListLoadingState initial value
fix(ui): Set `RoomListLoadingState` initial value
2023-09-18 09:55:15 +02:00
Ivan Enderlin 1cf5f3a0ee fix(ui): Set RoomListLoadingState initial value.
`RoomListLoadingState` was initialized with the `NotLoaded` state. This
is always true… except when there is a cache! If the cache contains
N rooms, the loading state should be `Loaded { … }`. Then the next
sync (if any) will update the loading state to `Loaded { … }` with
an adjusted number of rooms or something like that, but semantically
speaking, the presence of the cache should result in a `Loaded` state.

This patch fixes this behavior, and also adds the tests.
2023-09-18 09:26:37 +02:00
Ivan Enderlin 8032c39fda at(ui): Use a dynamic limit over the RoomList entries
feat(ui): Use a dynamic limit over the `RoomList` entries
2023-09-15 17:06:51 +02:00
Ivan Enderlin b6af934635 chore(ui): let + else FTW! 2023-09-15 16:51:08 +02:00
Damir Jelić 3fb6f206fc Record the HTTP method in our logs 2023-09-15 16:51:05 +02:00
Damir Jelić b61f2fbdc4 Test if we're doing the correct request when marking a room as a DM 2023-09-15 16:51:05 +02:00
Damir Jelić a61205088b Fetch the account data from the server before marking rooms as DMs 2023-09-15 16:51:05 +02:00
Damir Jelić de90cf413b Add a method to retrieve global account data events from the server 2023-09-15 16:47:22 +02:00
Ivan Enderlin db1125d427 chore(cargo): Back to regular eyeball. 2023-09-15 15:02:04 +02:00
Ivan Enderlin a5e19201df chore(ui): Produce Clear instead of Truncate { length: 0 }. 2023-09-15 14:43:21 +02:00
Ivan Enderlin 16bfd14b81 !fixup 2023-09-15 14:35:03 +02:00
Ivan Enderlin be6da14f16 test(ui): Add more tests. 2023-09-15 13:46:41 +02:00
Ivan Enderlin d12eda5839 feat(ui): RoomList provides entries that are limited.
For some room lists, the number of entries can be gigantic. For example,
some accounts have 800, 2500, or even 4000 rooms! It's not necessary for
the client/app to display all the 4000 rooms. First, it can create some
performance issues, second, nobody will scroll 4000 rooms to search for
a particular room :-). Such users are more likely to use a search bar or
something equivalent. The idea is that `RoomListService` will continue
to sync all the data, but only a _limited_ version of it will be shared
to the client/app.

This patch takes `RoomList::entries_with_dynamic_filter`, and improves
it to include this (dynamic) limit.

This patch renames `RoomList::entries_with_dynamic_filter`
to `::entries_with_dynamic_adapters`. It now returns a
`RoomListDynamicEntriesController`, which is a renaming of
`DynamicRoomListFilter`. Basically, the “dynamic filter” becomes a
“dynamic controller” because `RoomList::entries_with_dynamic_adapters`
manages more than a filter. It now uses
`eyeball_im_util::vector::DynamicLimit` to dynamically limit the size
of entries. And that's the major idea behind this patch.

`RoomListDynamicEntriesController::set` is renamed `::set_filter`, and 2
new methods are introduced: `add_one_page` and `reset_to_one_page`.

A _page_ is like a chunk of room entries we want to view or add. When
doing `next_page`, the limit increases to `old_limit + page_size`. The
`reset_pages` method resets the `limit` to `page_size` only.
2023-09-15 13:34:20 +02:00
Benjamin Bouvier 774f695eb8 chore(doc): fix documentation 2023-09-15 13:03:11 +02:00
Benjamin Bouvier ab0982e512 chore(matrix auth): simplify a bit the code of matrix auth's refresh_access_token 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 0cb5f666ae chore(oidc): remove a few unnecessary wrappers, now that OidcCtx is in an Arc'd data structure 2023-09-15 13:03:11 +02:00
Benjamin Bouvier b9b042ec4a chore(auth): prefix Session data structures with the auth kind 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 7665b15c5a chore(auth): prefix SessionTokens data structures with the auth kind 2023-09-15 13:03:11 +02:00
Benjamin Bouvier b42cb1c43f chore(oidc): rename OidcContext to OidcCtx for symmetry with AuthCtx 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 2acf21fcd4 refactor(oidc): move AuthCtx::authentication_server_info into OidcContext 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 3144d87c3a refactor(oidc): put the OidcContext in the AuthCtx 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 7e142c8132 refactor(oidc): lower cognitive load by removing RegisteredClientData 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 23f4aedf47 chore(oidc): update some code comments 2023-09-15 13:03:11 +02:00
Benjamin Bouvier 6db19198fc chore(oidc): restore_registered_client doesn't need to be async 2023-09-15 13:03:11 +02:00
Benjamin Bouvier dde2f408c5 chore(sliding sync): log the room id in the limited flag computation 2023-09-14 19:14:52 +02:00
Benjamin Bouvier b749b3546f feat(auth): make the session callbacks work for the matrix auth scheme too 2023-09-14 17:42:56 +02:00
Jorge Martín 6c45e56d61 ffi: Add Client.remove_avatar function 2023-09-14 17:02:23 +02:00
Jonas Platte 84daf1f079 ffi: Add RoomListItem::room_info_blocking 2023-09-14 16:49:36 +02:00
Nicolas Mauri 6d8d174a5e ffi: RoomInfo notification mode must reflect the user-defined mode (#2545) 2023-09-14 14:23:32 +02:00
Jonas Platte fd17bce300 ui: Fix day divider logic
… for when a remote event is re-received while a local echo is pending.
Also simplify test_togglling_reaction integration test so it still passes.
2023-09-14 14:14:44 +02:00
Benjamin Bouvier 67b0305a3e feat: Add a cross-process lock mechanism for OIDC token refresh (#2440)
This adds a cross-process lock for refresh to work correctly.

We want to coordinate token refresh across multiple processes. For that, we're using a cross-process lock, and a value in the database identifying the latest session tokens that are valid (a hash of the actual tokens, for security reasons).

Whenever we run into an HTTP error indicating that the tokens have been invalidated, we try to refresh the access tokens; that's already existing prior to this PR. The novelty introduced is that we take a cross-process lock before doing so, now. Taking this lock will also load a session hash from the database, and we'll compare it against the latest "known" session hash (that the current process saved into its memory).

If there's no mismatch (i.e. the database and the currently known are the same), then we're all good and can keep going with the refresh, synchronize the hashes everywhere (in-memory and database), make sure the client is notified about it (through a new user-provided callback `SaveSessionCallback`; on iOS this will save it into the device's keychain).

Otherwise, that means another process has done a refresh under our feet. In that case, we ask an authoritative source for trusted session tokens. On iOS, they're reloaded from the device keychain; that happens through a new user-provided callback `ReloadSessionCallback`. Then, we make sure that the DB and the in-memory value recall this latest value.

An embedder who would like to make use of the cross-process locking mechanism should call `client.oidc().set_session_callbacks` and `client.oidc().enable_cross_process_refresh`. If only interested with the pings for new sessions, the client may only call `client.oidc().set_session_callbacks`.

Fixes https://github.com/matrix-org/matrix-rust-sdk/issues/2418.
Fixes https://github.com/matrix-org/matrix-rust-sdk/issues/2476

## Future improvements

- More testing of the whole flow. Not sure if mocking will be quite fit for OIDC, as this may require setting up an HTTPS server for the authentication code exchange and other OIDC-specific flows.
- Get rid of `SessionChange`, which duplicates in some way how a client can be notified about session changes.

---

* chore: replace manual StateMemoryStore::new with derived Default

* feat: add store backing for cross-process locking in state store

* chore: rename CryptoStoreLock to CrossProcessStoreLock

* chore: generalize cross-process lock

* feat: move the cross-process locking mechanism to the main crate

* feat: add support for cross-process store lock in the state store 🥳

* feat: implement a cross-process lock for OIDC token refresh

* chore: tweak comment + function name

* feat: make restore_session safe wrt cross-process lock

* feat: add FFI method + add mechanism to reload from keychain

* fix rename

* feat: return early when there was another process refreshed tokens

* fix FFI compile error + tweak some comments

* fix: put the reload_session callback and cross-process locks behind Arc to share them across clients

* feat: Add session retrieval to FFI.

* HACKY; KIDS DON'T DO THIS AT HOME

* chore: log if the hash from db isn't the same from the one from the returned session

* make it simpler to test OIDC token refresh

* some work, that includes fixes and a first test

* feat: require that the reload_session_callback be set at the same time as the cross-process lock

* chore: traces, traces everywhere

* fix: inherit session_change_sender when creating the notification client

* Some FFI improvements to help with tokio problems

* feat: resilient mode when DB/callback disagree about session (callback wins!)

* chore: move sender.send to the finish_refreshing function

* feat: add a save_session callback in the FFI and use it to save the session in keychain while holding XP lock

* fix test expectation after adding the check 🤷

* feat: split the ClientDelegate into two parts, including brand new ClientSessionDelegate

* chore: get rid of lease lock impl in the state store, as it's now unused

* a mix of fmt + clippy

* feat: add ctor for the crossprocessrefreshlockctx

* Include user ID when retrieving session.

Necessary as this isn't known when creating the AuthenticationService.

* yo dawg, you can't block while you block

* share auth data between parent and child client, add lock, AAAAAA this is messy

* tweaks

* feat: make the cross-process store locks generic

And move the implementation to the common crate.

* chore: upgrade some code comments to doc comments in `OngoingMigration`

* feat: implement `CryptoStore::remove_custom_value`

As it's going to be used for the OIDC PR, so as to remove a remembered hash of session tokens.

* remove unneeded remnants

* correctly wait for current request to finish

* feat: make it possible to setup session delegates on android too(?)

* put the cross process stuff in its own file

* typos 🤷

* fix: detach before sending token refresh request, to make sure the response tokens are always properly saved

* kleepee

* First round of review, thanks jonas!

* review round 2. FIGHT

* remove useless logs + avoid using deref explicitly

* more specialized error when cross-process lock is enabled without session callbacks

* fix: avoid cyclic reference between the session callback and client

---------

Co-authored-by: Doug <douglase@element.io>
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-09-14 12:47:47 +02:00
Jonas Platte 0c4b8c602c ui: Sanitize m.room.message events in notifications
… including reply fallback stripping.
2023-09-14 12:18:30 +02:00
Jonas Platte 03bbdce15f ffi: Add in_reply_to field to MessageLikeEventContent::RoomMessage 2023-09-14 12:18:30 +02:00
Jonas Platte af400357f5 Use Self keyword more 2023-09-13 18:33:33 +02:00
Jonas Platte 4da3806a01 indexeddb: Simplify filter_map closure 2023-09-13 18:33:33 +02:00
Jonas Platte 1cbcee4fea Use as_variant crate for shorter code 2023-09-13 18:33:33 +02:00
Jonas Platte 1542abd25a ui: Adjust logs to use natural numbering 2023-09-13 15:17:55 +02:00
Doug 3b685e01b5 feat(bindings): Expose account management action in the bindings. 2023-09-13 14:14:02 +02:00
Doug 7d8c6521ed feat(sdk): Add an action parameter to the OIDC account URL. 2023-09-13 14:14:02 +02:00
Jonas Platte 9a30878f43 ui: Fix a typo 2023-09-13 11:45:11 +02:00
Jonas Platte 958ed1855e ui: Log a warning when TimelineInnerStateTransaction is cancelled 2023-09-13 11:45:11 +02:00
Jonas Platte a1d730f87b ui: Simplify TimelineInner subscription methods 2023-09-13 11:45:11 +02:00
Jonas Platte fa0d949600 ui: Change TimelineInnerStateTransaction to be committed explicitly 2023-09-13 11:45:11 +02:00
Jonas Platte be4c376423 ui: Use ObservableVectorTransaction for timeline 2023-09-13 11:45:11 +02:00
Jonas Platte 55be56e78f ui: Move some functions to TimelineInnerMetadata 2023-09-13 11:45:11 +02:00
Jonas Platte c6fd3ec4b0 ui: Split non-items fields of TimelineInnerState into separate struct
… as a preparation for further refactorings.
2023-09-13 11:45:11 +02:00
Jonas Platte 7168df8b30 ui: Fix indentation 2023-09-13 11:45:11 +02:00
Jonas Platte 7fad343390 ui: Remove unused import 2023-09-13 11:45:11 +02:00
Jonas Platte 768f062e0c ui: Ignore flaky test
It's still compiled, but not run unless `--ignored` or `--include-ignored`
is passed on the commandline.
2023-09-13 11:40:54 +02:00
Jonas Platte 96ccd6e2bd sdk: Fix unit tests not compiling without testing feature 2023-09-12 18:31:15 +02:00
Jonas Platte a4101e2f45 Upgrade Ruma 2023-09-12 17:19:56 +02:00
Benjamin Bouvier d35a8b7fa0 chore: remove one level of indent thanks to let else 2023-09-12 17:02:42 +02:00
Benjamin Bouvier 2d932dc29f feat: don't process the limited flag for e2ee-only sliding syncs 2023-09-12 17:02:42 +02:00
Benjamin Bouvier 654a2f2495 fix(sliding sync): don't mark a response to a locally empty room as limited
When receiving a sliding sync room response for a room that had no local events in its timeline cache,
we'd mark the room as limited before, which is incorrect. It was made worse by the fact that later in
the code, we'd clear the local cache if a room was marked as limited, so this is a problem that would
repeat itself over time (assuming empty responses for that room).

This fixes it and unifies logs so there's only one log line per room, at most.

Fixes https://github.com/vector-im/element-x-android/issues/1281
Fixes https://github.com/matrix-org/matrix-rust-sdk/issues/2540
2023-09-12 17:02:42 +02:00
Jonas Platte a53bfe5748 Some documentation cleanup 2023-09-12 09:25:19 +02:00
Benjamin Bouvier b565acd462 feat: log the x-sentry-event-id if we receive it from the server 2023-09-11 14:41:42 +02:00
Jonas Platte 6131e41183 sdk: Replace deprecated function 2023-09-08 12:35:01 +02:00
Jonas Platte d4904a01b0 Upgrade dependencies
Most notably eyeball-im-util 0.3.1, which includes an important bugfix.
2023-09-08 12:35:01 +02:00
Damir Jelić c32f2444fc Use the base64 encoding/decoding methods from vodozemac in the bindings 2023-09-08 11:43:32 +02:00
Damir Jelić 72e3079aab Use the base64 encoding/decoding functions from vodozemac 2023-09-08 11:43:32 +02:00
Damir Jelić 7e06ad130c Add a base64 prefix to the names of the base64 encoding/decoding functions 2023-09-08 11:43:32 +02:00
Richard van der Hoff 8d1308ef6a fix doc 2023-09-07 22:31:34 +01:00
Richard van der Hoff 783adb424e Impmenent Store::identity_stream_raw
An alternative to `user_identity_stream`, which does not hold a reference to
the `CryptoStore` and hence is less prone to leaking references
2023-09-07 22:17:09 +01:00
Doug eb865f490a chore(bindings): Handle OIDC metadata changes. (#2503)
Currently when the AuthenticationService is given updated metadata, it is ignored if a dynamic registration has already been made for a selected issuer. This PR fixes that by storing the metadata's hash and resetting the store when there is a mis-match.

Additionally it moves OidcRegistrations out of the FFI into a new authentication module in the UI crate and adds some tests.
2023-09-07 17:28:58 +00:00
dependabot[bot] 4991450d6a chore(deps): bump webpki from 0.22.0 to 0.22.1
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.1.
- [Commits](https://github.com/briansmith/webpki/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-07 18:51:15 +02:00
Mauro Romito fa91a74452 feat(bindings): upload user avatar 2023-09-07 18:12:16 +02:00
Doug d7f5cd51e4 chore(bindings): Add missing contacts field on OidcConfiguration. 2023-09-07 17:43:20 +02:00
Jonas Platte e655490b9f ffi: Add is_threaded method to timeline Message object 2023-09-07 16:54:21 +02:00
Jonas Platte 36942e4f22 ui: Add threaded property to timeline Message type 2023-09-07 16:54:21 +02:00
Jonas Platte 619085a190 Use ObservableVectorTransaction for room list 2023-09-07 15:30:18 +02:00
Jonas Platte 71cc7318ca Upgrade eyeball-im, eyeball-im-util 2023-09-07 15:30:18 +02:00
Benjamin Bouvier dbf9e80c8f encryption sync: disable the shared pos in the encryption sync
It is racy and would require a cross-process lock held during the whole
flow (from creating the request to processing the response).
2023-09-07 14:55:55 +02:00
Benjamin Bouvier ab7ec1bc38 feat: implement CryptoStore::remove_custom_value
As it's going to be used for the OIDC PR, so as to remove a remembered hash of session tokens.
2023-09-07 11:41:22 +02:00
Benjamin Bouvier 87be58bc2c chore: upgrade some code comments to doc comments in OngoingMigration 2023-09-07 11:41:22 +02:00
Benjamin Bouvier 685cc2bbc3 feat: make the cross-process store locks generic
And move the implementation to the common crate.
2023-09-07 11:41:22 +02:00
Ivan Enderlin 243cc6773a chore(ffi): Make Clippy happy. 2023-09-07 11:27:06 +02:00
Ivan Enderlin 4e50bcddc2 feat(ffi) Room::members returns a RoomMembersIterator.
This patch updates `Room::members` to return
`Result<Arc<RoomMembersIterator>, ClientError>`. This
`RoomMembersIterator` type is new, and is implemented in this patch too.

The idea behind this patch is to allow the bindings to “paginate” over
the list of members for a particular room, in case the room has 17k
members for example.
2023-09-07 11:03:16 +02:00
Ivan Enderlin 08424366d8 feat(ffi): Add ChunkIterator<T>.
This patch implements a generic `ChunkIterator` type. It's not tailored
for use via FFI, but it can be embedded inside a `uniffi::Object` for
example.
2023-09-07 11:01:39 +02:00
Benjamin Bouvier 9802795d8d fix: don't overwrite the parent session when creating a child Client 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 7ba06c3136 chore: unify implementations of SendRequest::into_future and Client::send_with_homeserver 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 0d6e12f3cd chore: tweak implementation of Oidc::set_session_tokens to make it less contrived 2023-09-07 10:34:54 +02:00
Benjamin Bouvier bb82a068c2 chore: move the auth_data field into AuthCtx 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 6c55767c73 chore: move the session_changer_sender field into AuthCtx 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 7db45a4b23 chore: move the refresh_token_lock into the AuthCtx 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 4802a50609 chore: put handle_refresh_tokens in the AuthCtx 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 28ab8e9efc chore: remove Client::authentication_server_info as it's duplicated from Oidc::authentication_server_info 2023-09-07 10:34:54 +02:00
Benjamin Bouvier 44a13fac9f chore(client): introduce AuthCtx to contain all data relative to auth(entication|orization) 2023-09-07 10:34:54 +02:00
Ivan Enderlin 74c5c2825a test(ci): Exponential backoff when retrying flaky tests
test(ci): Exponential backoff when retrying flaky tests
2023-09-07 09:51:17 +02:00
Ivan Enderlin f08524baa6 test(ci): Exponential backoff when retrying flaky tests.
This patch changes the backoff strategy from `fixed` to `exponential`
when a flaky test is retried. The `count` value is also updated to
3. Finally, we try to avoid the thundering herd problems with `jitter
= true`.
2023-09-07 09:12:29 +02:00
Ivan Enderlin b8dd45546d doc(sdk): Add documentation. 2023-09-07 09:06:16 +02:00
Ivan Enderlin 29fecf5109 test(ui): Adjust request_margin to avoid a flaky test
test(ui): Adjust `request_margin` to avoid a flaky test
2023-09-06 20:50:44 +02:00
Ivan Enderlin c5e4ea4e3c test(ui): Adjust request_margin to avoid a flaky test. 2023-09-06 20:34:57 +02:00
Damir Jelić 9174f120b4 Expose the user identities and devices streams in the main crate 2023-09-06 19:00:37 +02:00
Damir Jelić 578c1a473a Broadcast new and updated devices 2023-09-06 19:00:37 +02:00
Damir Jelić 53c4735944 Broadcast new and updated user identities 2023-09-06 19:00:37 +02:00
Damir Jelić 7419b2c86b Add our user ID to the CryptoStoreWrapper
This will become useful once we start broadcasting user identity
updates, we'll need to know which user identity is our own.
2023-09-06 19:00:37 +02:00
Ivan Enderlin 64bdbdcbaa feat(ui): Implement RoomListService::sync_indicator
feat(ui): Implement `RoomListService::sync_indicator`
2023-09-06 17:56:11 +02:00
Ivan Enderlin 77cc84a6d9 feat(ffi): Room::fetch_members no longer return a TaskHandle.
This patch changes the behavior of `Room::fetch_members. Since it's now
an async method, it no longer needs to return a `TaskHandle`.
2023-09-06 17:44:48 +02:00
Ivan Enderlin f3d33efa0f feat(ffi): Make fetch_members, members and member async.
This patch makes the following methods on `Room` async: `fetch_members`,
`members`, and `member`.
2023-09-06 17:37:05 +02:00
Ivan Enderlin b8ed566d78 feat(ffi): Add bindings for RoomListService::sync_indicator.
This patch adds the `RoomListService::sync_indicator` method, along
with the `RoomListServiceSyncIndicatorListener` callback interface, and
`RoomListServiceSyncIndicator` enum.
2023-09-06 17:16:36 +02:00
Ivan Enderlin 8a1ff1967f feat(ui): Implement RoomListService::sync_indicator.
This patch implements a new method: `RoomListService::sync_indicator`.
It returns a `impl Stream<Item = SyncIndicator>` where `SyncIndicator`
is a new enum with 2 variants: `Show` and `Hide`.

`SyncIndicator` is the UI equivalent of a sync spinner/loader/toaster,
that the app might want to show to the user to indicate when a _first_
request is sent and might be slow, i.e. the first response is taking
a little bit of time to come. The term _first_ may be innapropriate as
it covers the actual first sync request, but also the recovering sync
request. It means that when a sync error happened, the sync indicator
will be shown too, which is a pretty useful information for the user.

It's not because a `SyncIndicator` should be shown that it must be send
immediately onto the `Stream`. In case of a normal network conditions,
without any delay, it can lead to a “blinking” visual effect. Some
constants configure how long it takes to consider that a request is
“slow”, and that the `SyncIndicator` is necessary to be shown (or
hidden).
2023-09-06 17:16:36 +02:00
Jonas Platte 2787d058de ui: Remove Option from return type
… of TimelineItemContent::from_suitable_latest_event_content.
It had no branches that returned None.
2023-09-06 12:59:08 +02:00
Jonas Platte 57ba63d432 base: Allow redacted events as latest event 2023-09-06 12:59:08 +02:00
Ivan Enderlin acb731af2f fix: & without an explicit lifetime name cannot be used here
fix: `&` without an explicit lifetime name cannot be used here
2023-09-06 11:04:50 +02:00
Richard van der Hoff 3f3f599877 Add OlmMachine::get_room_event_encryption_info (#2510)
Since the verification status of an event can change, we need to be able to
refetch the verification status without doing the whole decryption dance.

Hence, we expose a new `get_room_event_encryption_info` method.
2023-09-06 10:03:15 +01:00
Richard van der Hoff d116e31733 Implement new CryptoStoreWrapper (#2515)
... so that `VerificationStore` and `StoreInner` can share the same
`save_changes` impl which broadcasts updates to the broadcast channels.
2023-09-06 09:57:40 +01:00
Ivan Enderlin f13282ea30 fix: & without an explicit lifetime name cannot be used here.
This was previously accepted by the compiler but is being phased out;
it will become a hard error in a future release! See https://github.com/
rust-lang/rust/issues/115010.
2023-09-06 09:37:51 +02:00
Benjamin Bouvier 896f62eb68 fix(tests): don't reuse the same store name in multiple tests 2023-09-05 18:45:51 +02:00
Benjamin Bouvier cc469f6d4a chore: derive Default for the state MemoryStore 2023-09-05 15:44:31 +02:00
Jonas Platte 2d47aecd37 Remove the appservice feature from matrix-sdk, matrix-sdk-test 2023-09-05 15:40:38 +02:00
Jonas Platte 7d674b39aa Remove matrix-sdk-appservice
There is unfortunately no capacity for maintaining it as a first-party
component of the Rust SDK.
2023-09-05 15:40:38 +02:00
Benjamin Bouvier 11b3be1a03 feat(notification client): always retry decryption if it failed before
Now that we can decrypt on both of {single|multi} process setups (i.e. available for both android
and ios), we can enable retrying decrypting notifications by default.
2023-09-05 14:17:41 +02:00
Benjamin Bouvier f13adb24dd feat(sync service): enable the encryption sync by default 2023-09-05 14:17:41 +02:00
Benjamin Bouvier 94cfa9fc12 chore(tests): move check_requests from encryption_sync_service to sliding_sync helper file 2023-09-05 14:17:41 +02:00
Benjamin Bouvier 3d89d750fb chore: rename EncryptionSync to EncryptionSyncService 2023-09-05 14:17:41 +02:00
Benjamin Bouvier 242c5bcb37 chore(ui): move xyz/mod.rs to xyz.rs for the encryption_sync and sync_service 2023-09-05 14:17:41 +02:00
Benjamin Bouvier d6f0635023 chore: clippy + review feedback 2023-09-05 11:17:14 +02:00
Benjamin Bouvier ea2826aac6 chore(FFI): update bindings for changes to the NotificationClient builder 2023-09-05 11:17:14 +02:00
Benjamin Bouvier 508091af80 feat: use the encryption sync permit from the SyncService in the NotificationClient
Also rejigger the parameters passed to the notification client builder, so that it's always required to pass
a process setup. With that, we're one step closer to removing the retry_decryption() function and enable it
by default.
2023-09-05 11:17:14 +02:00
Benjamin Bouvier de9b6d25cd feat: have the SyncService own the EncryptionSyncPermit and add try_get_encryption_sync_permit 2023-09-05 11:17:14 +02:00
Benjamin Bouvier 9469b77741 feat: add an EncryptionSyncPermit object allowing to use an EncryptionSync
The comment above the type should help understanding what it is about.
2023-09-05 11:17:14 +02:00
Jonas Platte 06ec19b50b ui: Add test for transfering reply details 2023-09-05 10:39:48 +02:00
Jonas Platte bd99c5e72b ui: Transfer reply details from old to new item when receiving dup event 2023-09-05 10:39:48 +02:00
Jonas Platte bdddb0ce7a ui: Move reply test into a separate module 2023-09-05 10:39:48 +02:00
Jonas Platte 73a9cd40d3 sdk: Remove warning about event without txn ID
… it doesn't make sense to have as long as it's still very common to get
duplicate non-echo events from the SS proxy.
2023-09-05 10:39:48 +02:00
Ivan Enderlin 465c9bcbed test(ui): Test Timeline is reset when a user is ignored/unignored. 2023-09-04 17:39:02 +02:00
Jonas Platte 4f2477fbe8 ui: Reset the timeline when ignore user list changes 2023-09-04 17:39:02 +02:00
Ivan Enderlin cf419566e8 feat(ui): TimelineInnerStateLock::lock is replaced by ::read and ::write
feat(ui): `TimelineInnerStateLock::lock` is replaced by `::read` and `::write`
2023-09-04 15:44:13 +02:00
Damir Jelić e835d9a1cc The export_room_key method does not encrypt the room keys nor does it panic 2023-09-04 15:39:10 +02:00
Ivan Enderlin 91784ded72 feat(ui): TimelineInnerStateLock::lock is replaced by read and write.
The `Timeline` batches its updates to its subscribers (e.g. a client app,
like Element X). A batch is built every time the inner state lock of
the `Timeline` is released. On the paper, it's nice; in practise, even
a read operation on the `Timeline` leads to building a new batch. This
is inefficient and consumes resources (like CPU cycles, FFI boundary
crossings, memory allocations etc.) even if there is no update to put in
the batch: this will just batch empty updates.

To avoid that, one needs to make the difference between read or write
operations onto the inner state. Only the write operations will fire a
batch.

This patch splits the `TimelineInnerStateLock::lock` method into
`::read` and `::write`. The idea is that a read-only lock doesn't
hold a clone of the lock release observer (`lock_release_ob:
SharedObservable<()>`), it will not notify the observer. Then it's only
the write lock that holds a clone of the lock release observer, and will
notify it.

This patch updates the code accordingly as best as possible.
2023-09-04 15:05:52 +02:00
Benjamin Bouvier e8e7738dfa chore: introduce fail! macro to avoid repetitive work 2023-09-01 16:06:46 +02:00
Benjamin Bouvier 2d5f5879ab chore: remove spurious clone 2023-09-01 16:06:46 +02:00
Benjamin Bouvier 0162e62feb fix: don't save the latest_id_token upon refresh (thanks @zecakeh!) 2023-09-01 16:06:46 +02:00
Benjamin Bouvier d37656d9f0 chore: use a hash() function instead of hashing manually 2023-09-01 16:06:46 +02:00
Benjamin Bouvier 5451d39ba3 chore: move notification within the refresh_access_token_inner function 2023-09-01 15:15:02 +02:00
Benjamin Bouvier a17a7608f3 chore: add more focused logs for OIDC 2023-09-01 15:15:02 +02:00
Benjamin Bouvier 4cdef7255e fix: save the OIDC refresh token from the response (!) 2023-09-01 15:15:02 +02:00
Jonas Platte 2b18a02488 ffi: Use avatar_url from sliding sync for RoomInfo where applicable 2023-09-01 10:47:48 +02:00
Jonas Platte db565fcff3 ci: Improve caching for matrix-rust-components-swift and tarpaulin 2023-09-01 10:43:52 +02:00
Jonas Platte e02676616f sdk: Make use of clonable FnOnce in event handlers 2023-09-01 10:42:22 +02:00
Jonas Platte a6b4e04181 sdk: Revert observable diff buffer capacity change 2023-09-01 10:06:07 +02:00
Benjamin Bouvier d060ec2830 chore: add e2e-encryption cfg guards 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 51dcdac46d TERRIBLE HACK: save the pos value in the crypto store 2023-08-31 16:08:45 +02:00
Benjamin Bouvier be3616c6b2 chore: rename restore_pos_from_database to share_pos 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 05a8343d03 chore: rename previous_pos to pos 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 28d25882c2 chore: fmt + clippy 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 5eb455032e tests: add test and maintain property that in-memory pos == db pos at all times 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 6b84d03e2f feat(encryption sync): restore the stream position from the database 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 08b9f0640c feat: add a new sliding sync option to restore the stream position from the database 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 4b67a6608e feat: persist previous pos when saving/restoring a sliding sync 2023-08-31 16:08:45 +02:00
Benjamin Bouvier 941ecbfe0d chore: refactor fields restored by restore_sliding_sync_state 2023-08-31 16:08:45 +02:00
Ivan Enderlin 719cbee96e feat(ui): Fine-tuning RoomList again
feat(ui): Fine-tuning `RoomList` again
2023-08-31 12:57:27 +02:00
Benjamin Bouvier 11404010e6 chore: disable colors for logging in FFI for logcat and stdout too 2023-08-31 12:13:25 +02:00
Ivan Enderlin 4b04f15bc5 chore(ui): Naming is hard :-). 2023-08-31 12:04:14 +02:00
Doug 7e71c79072 chore(sdk): Log the OIDC refresh token (hashed). (#2486)
* chore(sdk): Log the OIDC refresh token (hashed).
* chore(sdk): Fix Clippy and PR comments.
2023-08-31 09:25:46 +00:00
Benjamin Bouvier 50a3da386e feat: enable read receipts in the room list service sliding sync 2023-08-31 11:08:56 +02:00
Benjamin Bouvier eebf271d6c chore: use a workspace dependency for assert-json-diff 2023-08-31 10:34:46 +02:00
Benjamin Bouvier 4665277842 test(sliding sync): skipping b/o mode change doesn't change parameters in other lists
w
2023-08-31 10:34:46 +02:00
Ivan Enderlin 5a0c230a2d feat(ui): visible_rooms can shrink its timeline_limit.
This patch updates the behavior of `visible_rooms` where its
`timeline_limit` is shrunk and expanded when the state machine is
recovering.
2023-08-31 10:31:44 +02:00
Ivan Enderlin 7819938149 feat(ui): invites is always defined in RoomListService.
This patch updates `RoomListService` to install the `invites` sliding
sync list from the start, along with `all_rooms`. Prior to the patch,
`invites` was installed after the first sync.

`invites` is installed in selective sync-mode with a range of `0..=0`.
After the next sync, `invites` switched its sync-mode to growing with a
batch size of `0..=19`.
2023-08-30 21:20:29 +02:00
Jonas Platte 06a19f016e ffi: Only ever call ClientDelegate methods in a blocking task
… and clean up a few unnecessary Arc's.
2023-08-30 15:29:06 +02:00
Jonas Platte 5630851062 Upgrade Ruma 2023-08-30 15:22:00 +02:00
Ivan Enderlin 6b50a8988b Merge pull request #2463 from matrix-org/jmartinesp/add-x86-64-workaround-to-crypto-sdk-in-main
Add the x86-64 Android workaround to the Crypto SDK too
2023-08-30 15:12:17 +02:00
Jonas Platte 1311ddbae3 ffi: Disable colorization of tracing fields 2023-08-30 14:07:22 +02:00
Ivan Enderlin 5564d323f5 fix(ui): Try to make RoomListService fast for gigantic accounts with various network speeds
fix(ui): Try to make `RoomListService` fast for gigantic accounts with various network speeds
2023-08-30 14:06:23 +02:00
Ivan Enderlin 3062f30e08 chore(ffi): Add the RoomListServerState::Recovering variant. 2023-08-30 13:17:05 +02:00
Ivan Enderlin e8815d83b8 feat(ui): Add a new Recovering intermediate state in RoomListService.
This patch takes inspiration of
https://github.com/matrix-org/matrix-rust-sdk/pull/2480.

This patch adds a new `Recovering` state, which is a “transition”
between `Error` and `Terminated` to `Running`. When moving from `Error`
or `Terminated` to `Recovering`, `all_rooms`' sync-mode is set to
`Selective` with its initial range. Then when moving from `Recovering`
to `Running`, `all_rooms`' sync-mode is set to `Growing` with its
initial range again. For the `invites` list, it is reset to its initial
range when moving to `Recovering` too.

`Error` and `Terminated` now act the same.
2023-08-30 13:13:16 +02:00
Jonas Platte ca0c1f567a sdk: Add widget::EventFilter matching
Co-authored-by: Timo K <toger5@hotmail.de>
Co-authored-by: Daniel Abramov <inetcrack2@gmail.com>
2023-08-30 12:57:54 +02:00
Jonas Platte 04fee2952f sdk: Split widget EventFilter into sub-types
… and move them into their own module.

Co-authored-by: Timo K <toger5@hotmail.de>
Co-authored-by: Daniel Abramov <inetcrack2@gmail.com>
2023-08-30 12:57:54 +02:00
Jonas Platte c589bd0cd1 sdk: Add extra docs for widget channels 2023-08-30 12:57:54 +02:00
Jonas Platte 959b594c43 sdk: Rename widget::Info to WidgetSettings 2023-08-30 12:57:54 +02:00
Alfonso Grillo c567f963d5 ffi: Add send fn's for poll response and poll end 2023-08-30 09:44:08 +00:00
Ivan Enderlin e3aec5ebbc fix(ui): Update batch_size of all_rooms to 100.
This patch updates the `batch_size` of `all_rooms` once in growing
sync-mode to 100. It was previously increased from 50 to 200 in
12a1f25ec3. In some situations, it
generates very large payloads that can take time to be delivered to the
user with a slow network.

This patch tries to fine-tuned the `batch_size` value.
2023-08-30 08:48:55 +02:00
Ivan Enderlin e5a5dac099 fix(ui): Revert timeline_limit to 1 for all_rooms.
This patch reverts b2cc279279. On the
paper, it was a good idea. In practise, it has revealed a bug on the
server side. The only solution to mitigate this bug for now is to revert
the `timeline_limit` to 1.
2023-08-30 08:38:05 +02:00
Nicolas Mauri 1cc11ae988 feat(ffi): Add a notification_mode property to room_info (#2460)
* feat(ffi): Add a notification_mode property to room_info
* feat(sdk): Add a notification_mode() function to Room
* Fix Rust syntax
2023-08-29 12:25:26 +00:00
Doug 58d30454ae feat(bindings): Add logo_uri to OidcConfiguration
… and make most OidcConfiguration properties optional.
2023-08-29 13:35:10 +02:00
Jonas Platte 0590543df1 ffi: Add custom tracing log formatter (#2464)
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-08-28 15:05:21 +00:00
Ivan Enderlin 30731d60d7 feat(sdk): Adjust room_list observable capacity
feat(sdk): Adjust `room_list` observable capacity
2023-08-28 16:58:05 +02:00
Benjamin Bouvier 98dafac236 feat: time how long it takes to restore lists from the cache 2023-08-28 16:52:43 +02:00
Benjamin Bouvier b6c658aefa feat: introduce timer helper 2023-08-28 16:52:43 +02:00
Ivan Enderlin c23e8863fc feat(sdk): Adjust room_list observable capacity.
Prior to this patch, we have increased the
`SlidingSyncListInner::room_list` capacity, to avoid trigger
`VectorDiff::Reset` as much as possible. Now that Element X is optimised
to handle larger diff, we can discrease this number, yeeeee :-).
2023-08-28 16:33:02 +02:00
Ivan Enderlin 9d9add39fb Merge pull request #2466 from Hywan/feat-ui-roomlist-all-rooms-timeline-limit-0 2023-08-28 15:29:27 +02:00
Ivan Enderlin b2cc279279 feat(ui): Change all_rooms to timeline_limit=0.
When a user has hundreds of rooms, `RoomListService` will fetch at worst
one event per room, which can generate large responses. Let's use a
`timeline_limit=0`.
2023-08-28 14:50:55 +02:00
aringenbach 8cb00510d2 ffi: use RoomMessageEventContentWithoutRelation for send / reply / edit 2023-08-28 11:26:51 +02:00
Nicolas Mauri 63196aa6aa fix(sdk): limit the number of retries when updating push rules. (#2461)
* fix(sdk): limit the number of retries when updating push rules.
* fix Rust format
2023-08-25 15:11:49 +00:00
Jorge Martín e7ad46f5a0 Fix formatting 2023-08-25 14:37:09 +02:00
Jonas Platte 2311edecf6 ui: Remove unused import 2023-08-25 14:14:56 +02:00
Jonas Platte aed9b20195 Silence clippy lint arc_with_non_send_sync on wasm 2023-08-25 14:14:56 +02:00
Jorge Martín 1d08ffa05e Add the x86-64 Android workaround to the Crypto SDK too 2023-08-25 13:31:12 +02:00
Jonas Platte e02aa6b132 ffi: Add Room::{room_info, subscribe_to_room_info_updates} 2023-08-25 11:39:57 +02:00
Jonas Platte 9a0869dff8 base: Add Room::subscribe_info 2023-08-25 11:39:57 +02:00
Jonas Platte 72b11d5cc6 sdk: Replace Arc<RwLock<_>> around RoomInfo with SharedObservable 2023-08-25 11:39:57 +02:00
Ivan Enderlin 8f754a4f05 fix(base): BaseClient::ignore_user_list_changes is no longer behind an Arc
fix(base): `BaseClient::ignore_user_list_changes` is no longer behind an `Arc`
2023-08-24 15:17:14 +02:00
Jonas Platte 320f5c2155 Rename feature flag for widgets 2023-08-24 14:55:55 +02:00
Jonas Platte 1c3dd38b97 ffi: Add widget API skeleton 2023-08-24 14:55:55 +02:00
Ivan Enderlin 0c21827a5a fix(base): BaseClient::ignore_user_list_changes is no longer behind an Arc.
This patch first off renames `BaseClient::ignore_user_list_changes_tx`
to `::ignore_user_list_changes`.

This patch then changes the type from `Arc<SharedObservable<_>>` to
simply `SharedObservable` as this type implement `Clone`. We basically
have a double-`Arc` here.

Finally, this patch adds documentation for this field.
2023-08-24 14:54:40 +02:00
Jonas Platte 7aa6981e37 Make update_timeline_item macro compatible with Rust 1.70
… by swapping the branches around. Previously, invocations meant to
match the found + not_found branch were actually hitting the other one
prior to Rust 1.71, likely due to parser supporting type ascription
(even though it was an unstable feature).
2023-08-24 13:42:44 +02:00
Ivan Enderlin c742affa13 feat(ui): Reduce and refresh the batch_size growing sync-mode of invites in RoomListService
feat(ui): Reduce and refresh the `batch_size` growing sync-mode of `invites` in `RoomListService`
2023-08-24 13:32:29 +02:00
Ivan Enderlin 55e8f2573b feat(ui): Add the ResetInvitesListGrowingSyncMode action in RoomList.
Inside `RoomListService`, the `State` enum handles the transition from
one state to another. In case of some `State::Error`, the `all_rooms`
sliding sync was refreshed, i.e. its sync-mode was reset to its initial
value.

This patch also refreshes the `invites` sliding sync list! It adds
the `ResetInvitesListGrowingSyncMode` action, and attaches it to the
`refresh_lists` actions.

New constants are added to represent default `batch_size` values for the
growing sync-mode for various sliding sync list. It could be helpful for
further maintenance.

This patch finally adds and updates the tests accordingly.
2023-08-24 13:07:28 +02:00
Ivan Enderlin e82607c743 chore(cargo) Update ruma
chore(cargo) Update `ruma`
2023-08-24 12:41:56 +02:00
Jonas Platte aac6294755 Rename test modules to tests
… for consistency.
2023-08-24 12:28:34 +02:00
Jonas Platte 3c8a59a43a Remove module::* imports 2023-08-24 12:28:34 +02:00
Ivan Enderlin 4e25a8c3c1 chore(cargo): Update ruma. 2023-08-24 12:17:40 +02:00
Ivan Enderlin 3dbc00cd67 feat(ui): Reduce the batch_size of invites in RoomListService.
This patch changes the `batch_size` of the sliding sync list `invites`
for `RoomListService`. Previous value was 100, new value is 20.

For accounts that have a large number of invites, it won't slow the
rendering of `visible_rooms`.
2023-08-24 10:36:04 +02:00
Marco Romano b2a1e3d268 Bump ruma 2023-08-24 07:37:10 +00:00
Jonas Platte 8deb0ff2e1 base: Handle redactions if e2ee is disabled 2023-08-23 17:20:26 +02:00
Jonas Platte 2fb6bdc24c Fix more clippy lints 2023-08-23 17:20:11 +02:00
Jonas Platte 24d2ccd5f0 base: Make BaseRoomInfo::handle_redaction private
It should only be called from RoomInfo::handle_redaction.
2023-08-23 17:19:51 +02:00
Jonas Platte 9fc5a7dc92 base: Add some logging for handle_redaction 2023-08-23 17:19:51 +02:00
Daniel Abramov 6ecd640ad7 Widget API: Initial skeleton 2023-08-23 15:08:51 +00:00
Marco Romano 3bc7b9136e ui: Add support for polls in timeline 2023-08-23 12:57:52 +00:00
Ivan Enderlin 4dbb0a7cc7 fix(ui): Tweak State of RoomListService when session is forced to expire
fix(ui): Tweak `State` of `RoomListService` when session is forced to expire
2023-08-23 13:28:08 +02:00
Ivan Enderlin 2dd1caaeab doc(sdk,ui): Fix typos. 2023-08-23 13:07:24 +02:00
Ivan Enderlin 6736a4a05f Update crates/matrix-sdk-ui/src/room_list_service/mod.rs
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2023-08-23 13:05:21 +02:00
Ivan Enderlin 8c234f0d0d test(ui): Test that RoomList::expire_sync_session ends up in Error. 2023-08-23 12:13:54 +02:00
Jonas Platte 4df1ee140a Fix clippy lints 2023-08-23 11:36:21 +02:00
Ivan Enderlin 0dacf6edc3 fix(ui): Tweak State of RoomList when session is forced to expire.
Usually, when the sliding sync session expires, it leads the state to
be `Error`, thus some actions (like refreshing the lists) are executed.
However, if the sync-loop has been stopped manually, the state is
`Terminated`, and when the session is forced to expire, the state
remains `Terminated`, thus the actions aren't executed as expected.
Consequently, this patch updates the state to `Error` manually.
2023-08-23 11:24:01 +02:00
Ivan Enderlin 5b28e641c4 Merge pull request #2441 from matrix-org/dependabot/cargo/rustls-webpki-0.101.4
chore(deps): bump rustls-webpki from 0.101.2 to 0.101.4
2023-08-23 09:17:11 +02:00
dependabot[bot] 9bf214925c chore(deps): bump rustls-webpki from 0.101.2 to 0.101.4
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.101.2 to 0.101.4.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.101.2...v/0.101.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-22 18:10:40 +00:00
Jonas Platte 0d24bcf6e5 Revert "bindings: Use new uniffi-bindgen build mode"
This reverts commit 329b6c4eb1.
2023-08-22 18:40:20 +02:00
Kévin Commaille e01421a3dd Upgrade Ruma
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-08-22 17:25:02 +02:00
Jonas Platte 329b6c4eb1 bindings: Use new uniffi-bindgen build mode 2023-08-22 11:12:55 +02:00
Jonas Platte ef0549b8b8 xtask: Use camino path types 2023-08-22 11:12:55 +02:00
Doug 60392c299d bindings: Add OIDC support to AuthenticationService
- Use OIDC for logout when appropriate.
- Allow server's that support OIDC but not passwords to work.
- Only sign out users if token refresh is explicitly refused.
- Expose the OIDC account URL.
- Support for RP initiated logout.
2023-08-22 09:06:32 +00:00
Benjamin Bouvier 4493cbf0ac chore: rework Room::sync_up so it's really async 2023-08-21 19:36:33 +02:00
Benjamin Bouvier b7e18352c4 fix: read RoomInfo data at the very last minute before saving it
Also take ownership of the sync lock in write mode, since this operation could run concurrently
to room_joined/room_left events.
2023-08-21 19:36:33 +02:00
Benjamin Bouvier 936b6980c9 chore: move SyncTokenAwareClient to the common test helpers 2023-08-21 19:36:33 +02:00
Benjamin Bouvier e262db3505 test: write an integration test for missing keys during encryption 2023-08-21 19:36:33 +02:00
Benjamin Bouvier 0d67d14201 feat: query keys for newly sync'd members when sending encrypted message 2023-08-21 19:36:33 +02:00
Jonas Platte 78ad6a6530 ui: Remove JSON on RemotEventTimelineItem when it's redacted locally 2023-08-21 15:54:25 +02:00
Jonas Platte a5a541ec98 ui: Improve comment in redaction code
(move it to a more logical place)
2023-08-21 15:54:25 +02:00
Jonas Platte d395648165 ui: Remove outdated comment
The relevant decryption work is already done in a separate async task,
spawned in `TimelineInner::retry_event_decryption_inner`.
2023-08-21 15:00:00 +02:00
Benjamin Bouvier f5ab1084eb fix: temporarily use the pip install method for the setup-matrix-synapse action
Until https://github.com/michaelkaye/setup-matrix-synapse/issues/95 is properly resolved.
2023-08-21 11:54:25 +02:00
Benjamin Bouvier 81daf5c90a test(sliding sync): add test that caused a deadlock before this PR 2023-08-21 11:48:51 +02:00
Benjamin Bouvier c0b18c291d chore: make clippy happy 2023-08-21 11:48:51 +02:00
Benjamin Bouvier 6988bd1e6f feat(sliding sync): remove the response_handling_lock and extend the position's lock responsibilities
In the previous situation, we had two locks with similar responsibilities, the `response_handling_lock`
and the `position` lock. The latter *almost* covered the former's critical zone, albeit for a single
function call, which left room for a deadlock situation (latter taken, then former, then latter).

This removes the former, and extends the critical zone of the latter up to the end of the response handling,
removing the possibility of the deadlock entirely.
2023-08-21 11:48:51 +02:00
Ivan Enderlin 54771eadcf fix(ffi): Fix a keyword conflict with Swift
fix(ffi): Fix a keyword conflict with Swift
2023-08-21 11:35:09 +02:00
Ivan Enderlin 64f3bc674e feat(ui): Create a new normalized_match_room_name filter
feat(ui): Create a new `normalized_match_room_name` filter
2023-08-21 11:27:53 +02:00
Ivan Enderlin b78372d4bb chore(cargo): Update UniFFI. 2023-08-21 11:07:32 +02:00
Ivan Enderlin 102482c6e3 fix(ffi): Fix a keyword conflict with Swift. 2023-08-21 10:32:33 +02:00
Ivan Enderlin 9973d30700 feat(ffi): Implement RoomListEntriesDynamicFilterKind::NormalizedMatchRoomName. 2023-08-21 10:22:06 +02:00
Ivan Enderlin 2821807e14 feat(ui): Create a new normalized_match_room_name filter.
This patch creates a new `normalized_match_room_name` filter for
`RoomListService`.
2023-08-21 10:19:20 +02:00
Ivan Enderlin 14f8b33136 Merge pull request #2425 from matrix-org/jonny/message-content-from-html
Add binding to create message events from HTML
2023-08-21 09:48:35 +02:00
Ivan Enderlin e9a9382b2b RoomInfo: Remove is_encrypted use sliding sync room avatars
RoomInfo: Remove is_encrypted use sliding sync room avatars
2023-08-21 09:43:05 +02:00
jonnyandrew 5497a8de2c Add binding to create message event from HTML 2023-08-18 16:03:03 +01:00
Stefan Ceriu 531d1d9761 Read the room avatar URL from the RoomListService room instead of the SDK room so that DMs are populated correcty 2023-08-18 17:54:23 +03:00
Benjamin Bouvier 88018c259f Revert upgrade to uniffi since it broke generation of swift bindings 2023-08-18 12:07:36 +02:00
Stefan Ceriu 531d8d220d Remove is_encrypted from room info as it's not used and it might do network calls and slow down room list updates 2023-08-18 11:36:01 +03:00
Ivan Enderlin d077892ad4 chore(uniffi): Update uniffi to another branch
chore(uniffi): Update `uniffi` to another branch
2023-08-17 18:29:05 +02:00
Ivan Enderlin cc46417ec5 Merge branch 'main' into hywan/fix-android-uniffi-async-bis 2023-08-17 18:27:54 +02:00
Ivan Enderlin 4d7f951128 chore(cargo): Update UniFFI to main (specific commit). 2023-08-17 18:26:21 +02:00
Ivan Enderlin 92284a353e feat(sdk): SlidingSync is able to “ignore” some errors
feat(sdk): `SlidingSync` is able to “ignore” some errors
2023-08-17 16:50:12 +02:00
Ivan Enderlin 73bb46b5ea Revert "feat(sdk): SlidingSync makes timed out silent."
This reverts commit 9b811009e1.

We have realized that the server might not handle timeouts as expected.
Thus, it's hard to know the difference between network timeout and poll
timeout (resp. the server is unreachable vs. the server has nothing to
respond with). We will come back on this later.
2023-08-17 16:27:12 +02:00
Ivan Enderlin 9b811009e1 feat(sdk): SlidingSync makes timed out silent.
As a sequel of the previous commit (d4cbcd397d), this patch updates
`SlidingSync` to “ignore” timeouts, i.e. timeouts aren't reported to the
caller, and aren't stopping the sync-loop.
2023-08-17 15:37:25 +02:00
Ivan Enderlin d4cbcd397d feat(sdk): SlidingSync is able to ignore some errors.
All errors inside `SlidingSync` are stopping the sync-loop, and errors
are returned to the caller. However, in some situation, some errors
should be ignored, i.e. they should not stop the sync-loop and they
should not be returned to the caller: the sync-loop just continues to
run. This patch does that for `Error::ResponseAlreadyReceived`. More
errors will come.

Why is it annoying? When `matrix_sdk_ui::SyncService` sees an error,
it stops all the sync-loops (`RoomListService`, `EncryptionSync`…) and
restarts them properly. In the case of `Error::ResponseAlreadyReceived`,
this is a waste of time and resources. This error is an error from the
`SlidingSync` point of view, but _not_ from the caller point of view.
2023-08-17 15:35:51 +02:00
Ivan Enderlin 932b9dee6b fix(base): Detect left room in SlidingSyncResponse
fix(base): Detect left room in `SlidingSyncResponse`
2023-08-17 15:32:58 +02:00
Ivan Enderlin 2c9981050a fix(sdk): Preventing starting a new request if the previous didn't finish
fix(sdk): Preventing starting a new request if the previous didn't finish
2023-08-17 15:29:58 +02:00
Ivan Enderlin 2a6fc16549 chore: Use serde::Raw without any feature flag. 2023-08-17 14:30:25 +02:00
Ivan Enderlin 1aef72d105 feat(base): Left room are stored in the leave list in SlidingSync.
This patch updates the `BaseClient::process_sliding_sync` method to put
the `LeftRoom` in the `SyncResponse::leave` list.

To achieve so, this patch updates
`BaseClient::process_sliding_sync_room` to returns `Option<JoinedRoom>`,
`Option<LeftRoom>` and `Option<InvitedRoom>` (previously, it was only
`JoinedRoom` and `Option<InvitedRoom>`).
2023-08-17 14:30:25 +02:00
Ivan Enderlin acc2601c5b feat(base): Look for state events in timeline for SlidingSync.
SlidingSync emits state events inside `required_state`, but _also_
sometimes inside `timeline`! This patch looks for state events inside
both entries.

The rest of the patch is composed of tests.
2023-08-17 14:30:25 +02:00
Jonas Platte 47e7360b04 ffi: Add RoomInfo 2023-08-17 12:49:03 +02:00
Jonas Platte 9b803eda12 ffi: Merge impl blocks 2023-08-17 12:49:03 +02:00
Ivan Enderlin d946664cf0 doc(sdk): Fix a typo. 2023-08-17 11:56:21 +02:00
Ivan Enderlin f9f12c2b89 fix(sdk): Preventing starting a new request if the previous didn't finish.
Imagine the following scenario:

A request $R_1$ is sent. A response $S_1$ is received and is being
handled. In the meantime, the sync-loop is instructed to skip over any
remaining work in its iteration and to jump to the next iteration. As a
consequence, $S_1$ is detached, but continues to run. In the meantime, a
new request $R_2$ starts. Since $S_1$  has _not_ finished to be handled,
the `pos` isn't updated yet, and $R_2$ starts with the _same_ `pos`
as $R_1$.

The impacts are the following:

1. Since the `pos` is the same, even if some parameters are different,
   the server will reply with the same response. It's a waste of time
   and resources (incl. network).
2. Receiving the same response could have corrupt the state. It has been
   fixed in https://github.com/matrix-org/matrix-rust-sdk/pull/2395
   though.

Point 2 has been addressed, but point 1 remains to be addresed. This
patch fixes point 1.

How? It changes the `RwLock` around `SlidingSyncInner::position` to
a `Mutex`. An `OwnedMutexGuard` is fetched by locking the mutex when
the request is generated (i.e. when `pos` is read to be put in the new
request). This `OwnedMutexGuard` is kept during the entire lifetime
of the request extend to the response handling. It is dropped/released
when the response has been fully handled, or if any error happens along
the process.

It means that it's impossible for a new request to be generated and to
be sent if a request and response is running. It solves point 1 in case
of successful response, otherwise the `pos` isn't updated because of
an error.
2023-08-17 10:09:21 +02:00
Ivan Enderlin 2718176eab chore(sdk): Move response_handling_lock inside SlidingSyncInner.
This patch moves `SlidingSync::response_handling_lock` inside
`SlidingSyncInner`. There is no reason why it's stored inside
`SlidingSync`.
2023-08-17 09:16:25 +02:00
Ivan Enderlin db9012a45e feat(base): Improve Client::deserialize_events.
This patch first off renames `Client::deserialize_events` as
`Client::deserialize_state_events`. Then, this patch initially updated
the return type of this method from `Vec<Option<_>>` to `Vec<_>` to
filter out events that failed to deserialize. The patch ultimately
updates the return type of this method to `Vec<(Raw<_>, _)>`, so that
the raw state events that map to the state event are collected too (this
is required for making `Client::handle_state` to work correctly if an
event failed to deserialized). Finally, the rest of the patch updates
the code accordingly.
2023-08-16 11:29:35 +02:00
Kévin Commaille 0dac5080c6 experimental: Expose an OpenID Connect API
Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-08-16 10:39:18 +02:00
Ivan Enderlin 6c2d596603 test(sdk): Test members count.
This patch improves some existing tests, and adds more test for members
count.
2023-08-14 18:04:41 +02:00
Ivan Enderlin 758c3f7901 chore(ui): Improve error description for Error::SlidingSync
chore(ui): Improve error description for `Error::SlidingSync`
2023-08-14 15:33:39 +02:00
Ivan Enderlin bbe4df9cd6 chore(ui): Improve error description for Error::SlidingSync.
`matrix_sdk_ui::room_list_service::Error` has a `SlidingSync` variant to
report errors from `matrix_sdk::sliding_sync::Error` (to be more exact,
from `matrix_sdk::Error::SlidingSync`).

This patch updates the error message from `SlidingSync failed` to
`SlidingSync failed: <explanation>`, where `<explanation>` is the
`Display` representation of the inner error.

It will help to provide more details to the end-user when looking at
logs.
2023-08-14 15:03:38 +02:00
Ivan Enderlin 3b8f0c9a60 chore(ui): Raise a trace log into an error
chore(ui): Raise a `trace` log into an `error`
2023-08-14 14:42:25 +02:00
Ivan Enderlin b9d74d3643 feat(ui): Room::latest_event returns the Timeline local event if any
feat(ui): `Room::latest_event` returns the `Timeline` local event if any
2023-08-14 13:56:51 +02:00
Ivan Enderlin 061f8534c8 doc(ui): Update the comment of Room::latest_event. 2023-08-14 13:36:08 +02:00
Ivan Enderlin 6a7caa7257 Revert "feat(ui): Room::latest_event uses Timeline if it exists."
This reverts commit 2290bae942.
2023-08-14 13:27:56 +02:00
Ivan Enderlin f937920007 chore(ui): Raise a trace log into an warning.
The issue https://github.com/matrix-org/sliding-sync/issues/3 has been
fixed. It's safe to raise the `trace!` into an `warn!` now.
2023-08-14 13:24:28 +02:00
Ivan Enderlin 924033ed9c test(ui): Improve the remote_echo_full_trip test
test(ui): Improve the `remote_echo_full_trip` test
2023-08-14 13:23:02 +02:00
Ivan Enderlin c192343495 test(ui): Improve the remote_echo_full_trip test.
This patch improves the `remote_echo_full_trip` `Timeline` test to
ensure that until the event reaches the `Sent` state, it is indeed a
local echo.
2023-08-14 12:54:45 +02:00
Damir Jelić 4643bae284 Eliminate a race condition in the Room::request_encryption_state method
The Room::request_encryption_state method employs a DashMap to uphold a
lock, facilitating the de-duplication of requests directed to the server.

The de-duplication logic involves creating a fresh Mutex and embedding
it into the DashMap through these stages:

    1. Generate a new de-duplication mutex.
    2. Insert a mutex copy into the DashMap.
    2. Acquire the mutex.

Due to DashMap's limitation of enabling map locking solely during
insertion (step 1), a race condition emerges. Consequently, multiple
invocations of the Room::request_encryption_state method might
concurrently endeavor to insert the de-duplication mutex.

This commit removes the chance of such a race. It substitutes the
DashMap with a combination of a mutex and a BTreeMap. This adaptation
permits us to lock the map throughout all three specified operations.
2023-08-11 13:58:12 +02:00
Damir Jelić f3dd161b3a Resolve a bug causing incorrect results in Room::is_encrypted
This commit addresses a bug in the Room::is_encrypted functionality.
The issue stems from the Room::request_encryption_state method, which
Room::is_encrypted relies on. This method incorporates a de-duplication
mechanism to ensure that only a single request for the m.room.encryption
state event is made to the server.

Due to this de-duplication mechanism, Room::request_encryption_state
follows two code paths. One path receives the state event from the
server, while the other path merely waits for the first path to complete.

The primary goal of the Room::request_encryption_state method is to
furnish the requested state event. However, because the second code path
doesn't receive any content, it returns an Option.

The problem arises when Room::is_encrypted evaluates this Option. It
erroneously determines that if the Option is None, encryption remains
inactive for the room.

To rectify this, the commit proposes that Room::is_encrypted analyze the
information stored in the in-memory RoomInfo, which is maintained by the
Room::request_encryption_state method. This approach mirrors the existing
behavior of Room::is_encrypted when it processes the code path where the
m.room.encryption state event has already been retrieved.
2023-08-11 13:58:12 +02:00
Damir Jelić bd1b6e7a27 Instrument the Room::send_raw method 2023-08-11 13:58:12 +02:00
Damir Jelić eae8437321 Add a test for the Room::is_encrypted method 2023-08-11 13:58:12 +02:00
Jonas Platte d14e23a676 ffi: Remove tracing callsite column, make line optional 2023-08-11 12:10:58 +02:00
Jonas Platte f7d52dfdc1 ffi: Make tracing more flexible
… at the expense of some performance.
2023-08-11 12:10:58 +02:00
Benjamin Bouvier 6e62569b8a feat(sync service): don't intertwine the state of both sliding syncs (#2362)
## Feature

There are now three tasks running in the sync service:

- the room list sliding sync task
- the encryption sync task
- a new scheduler task, that listens to messages sent by both of the previous tasks, and will take care of cleaning up tasks, stopping the services, and setting states after it received a message.

When any of the sliding sync fails, it sends a message to the scheduler task, indicating why it stopped (error or not, was it an expired session or not). Then the scheduler task will do any necessary cleanup.

`stop()` (née `pause()`) can now make use of that scheduler task as well, by sending a report requesting to stop both tasks and services. Responsibilities are now cleanly split: in particular, I like it much better that the room list task doesn't have to stop the encryption sync itself, since it's not really its duty. (And this avoids lots of code duplication to cleanup tasks and stop services.)

## Testing

This also tests the `SyncService` states. Unfortunately it's not perfectly deterministic in two places:

- we can't predict how many requests will be sent to the server (although, with the mocking server responding in 50ms, and considering a waiting time of e.g. 300ms, it should send at least two requests).
- the test `pause()` and re`start()` the sync service at some point, and then we can't guess what the next `pos` will be, in any of the syncs: it could either be the previous value (if we aborted while processing the previous response), or the next value (meaning we could finish processing the sliding sync response).

Still, it confirms at least that pausing and resuming work as expected.

---

Fixes #2382
2023-08-11 08:55:48 +00:00
Jonas Platte 5943f5f7f3 sdk: Set content-length header when streaming HTTP request body 2023-08-11 09:21:58 +02:00
Alfonso Grillo 364a4ae5a9 ffi: Add create-poll API 2023-08-10 15:38:17 +00:00
Ivan Enderlin 0372467288 chore(uniffi): Update uniffi to another branch.
This patch switches UniFFI from
https://github.com/mozilla/uniffi-rs/pull/1684 to
https://github.com/mozilla/uniffi-rs/pull/1697.
2023-08-10 17:33:36 +02:00
Benjamin Bouvier 159786fe36 feat(notification client): bump sliding sync timeouts (#2403)
The timeouts were a bit too agressive, according to some user logs, resulting in failing to
load the event mentioned in the notification.

Here's the rationale for the new timeouts: we're only limited by the iOS process which has *at
most* 30 seconds to process a notification.

- We're running at most 3 requests of the notification sliding sync, so that will be (1+3)*3 = 12
seconds allocated for that.
- If we've found an event but it required decryption, we're running the encryption sync up to
2 times, (3+4) seconds each => 14 seconds.

At most we're eating up 26 seconds of the entire time, leaving some ballast for the rest of
the program.
2023-08-10 17:31:14 +02:00
Ivan Enderlin e9d6a9bfd7 chore: Remove unnecessary clones
chore: Remove unnecessary clones
2023-08-10 16:51:41 +02:00
Ivan Enderlin 361bc18757 fix(ui): Add m.room.member: $LAZY in the require states of visible_rooms
fix(ui): Add `m.room.member: $LAZY` in the require states of `visible_rooms`
2023-08-10 16:24:58 +02:00
Nicolas Mauri ac42d07de7 sdk+ffi: in notification settings, replace members_count by is_one_to_one for more clarity 2023-08-10 16:19:11 +02:00
Ivan Enderlin 2290bae942 feat(ui): Room::latest_event uses Timeline if it exists.
This patch optimises the previous patch by simplifying the use of
`Timeline`. If the `Timeline` exists, let's return the `latest_event`
every time: whether it is a remote _or_ a local event.
2023-08-10 16:00:06 +02:00
Ivan Enderlin e0163e67be feat(ui): Room::latest_event returns the Timeline local event if any.
This patch updates `Room::latest_event` to return the `Timeline` local
event if any.

First off, it starts by checking if a `Timeline` exists. It won't create
it if it doesn't exist! Second, it checks whether a latest event exists.
Finally it checks whether it's a local event.

This patch also updates the tests accordingly.
2023-08-10 15:59:50 +02:00
Ivan Enderlin e8cd21034b chore: Remove unnecessary clones.
The types used here are not implementing `Clone`, so calling `clone`
on them` copies the reference, which does not do anything and can be
removed.
2023-08-10 15:15:06 +02:00
Ivan Enderlin f64a46a2be Merge pull request #2396 from matrix-org/nicolas/notification_settings_user_defined_room_rules
sdk+ffi: Get the user-defined notification mode for a room and all rooms for which a user-defined rule exists.
2023-08-10 14:11:35 +02:00
Ivan Enderlin 0af918b8a7 feat(sdk): Skip Sliding Sync Response if it's been received already
feat(sdk): Skip Sliding Sync `Response` if it's been received already
2023-08-10 13:35:21 +02:00
Ivan Enderlin c5d2181549 test(sdk): Ensure that avatar is serialized as expected in FrozenSlidingSyncRoom
test(sdk): Ensure that `avatar` is serialized as expected in `FrozenSlidingSyncRoom`
2023-08-10 13:30:40 +02:00
Ivan Enderlin 2b938e3f03 doc(sdk): Fix a typo.
Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-08-10 13:17:15 +02:00
Ivan Enderlin 817690206c feat(sdk): Create SlidingSync::expire_session.
For the sake of clarity, this patch extracts some code into its own
`SlidingSync::expire_session`.
2023-08-10 12:14:42 +02:00
Jonas Platte 4bfcc6669d Use simpler construction functions for tokio::broadcast::Sender 2023-08-10 12:04:40 +02:00
Jonas Platte f8eefadb2d Upgrade tokio to 1.30 2023-08-10 12:04:40 +02:00
Richard van der Hoff f51bc47949 Fix rust doc on IndexedDBStore (#2394)
it's not in-memory
2023-08-10 11:47:56 +02:00
Ivan Enderlin 5d8b0c1a0d feat(sdk): Save positions at the end of handle_response. 2023-08-10 11:31:18 +02:00
Nicolas Mauri 8c6e3949e0 sdk+ffi: allow to get all room IDs for which a user-defined rule exists. 2023-08-10 10:02:11 +02:00
Nicolas Mauri 0a52fc895e ffi: Allow to get the user-defined notification mode for a room 2023-08-10 09:51:10 +02:00
Ivan Enderlin 51c25a4456 feat(ui): Implement RoomList::entries_with_dynamic_filter
feat(ui): Implement `RoomList::entries_with_dynamic_filter`
2023-08-10 08:28:43 +02:00
Ivan Enderlin d20e82380a test(ui): Adjust pos. 2023-08-09 18:07:18 +02:00
Ivan Enderlin 1f7ce2893b feat(sdk): Skip Sliding Sync Response if it's been received already.
A Sliding Sync `v4::Response` contains a `pos` value. It helps to identify
progress during several requests/responses. If two requests with the
same `pos` are sent, the server **must** reply with the same response,
as defined in the specification.

The corollary is: If a response contains a `pos` that has already
been received, the client **must** ignore it, otherwise it can create
duplications. For example, let a response containing sync operations,
like `DELETE` or `INSERT`, with indexes: if this pair of operations are
repeated twice with the same index, it creates a broken state.

To avoid this behaviour, this patch stores the last 20 position markers
received from the server. If a response contains a `pos` that has
already been received, a (new) error is returned. As with all the other
errors, the sync-loop is stopped, and must restarted. The past positions
survive to this restart, as for the rest of the state.

When the server replies with a `M_UNKNOWN_POS`, the `pos` and the
`past_positions` are all cleared.
2023-08-09 17:18:29 +02:00
Ivan Enderlin b1b0641150 test: Ensure that avatar is serialized as expected. 2023-08-09 15:30:26 +02:00
Ivan Enderlin 696e10bdad chore: Use async_test instead of tokio::test. 2023-08-09 15:30:13 +02:00
Ivan Enderlin 0f94f93080 chore: When LSP is broken… :-) 2023-08-09 15:18:24 +02:00
Ivan Enderlin 55ba580c0d chore: Simplify Fn declaration. 2023-08-09 15:08:05 +02:00
Ivan Enderlin da7e05f18f feat(ffi): Use an enum to set dynamic filter.
This patch removes
`RoomListEntriesDynamicFilter::set_with_fuzzy_match_pattern` and
replaces it by a single `::set` method. It takes a new enum as
parameter: `RoomListEntriesDynamicFilterKind`. This enum has a
`FuzzyMatchRoomName` variant to simulate the removed method. It also
adds the `All` variant, to represent the `all` filter.
2023-08-09 15:00:09 +02:00
Ivan Enderlin f35275f4fc feat(ui): Implement a new filter: all.
This patch implements a new `all` filter.
2023-08-09 14:51:15 +02:00
Ivan Enderlin 90682270ae feat(ffi): Implement RoomList::entries_with_dynamic_filter.
First, this patch makes `RoomList::entries` infallible.

Second, this patch implements `RoomList::entries_with_dynamic_filter`.
2023-08-09 14:16:23 +02:00
Ivan Enderlin 3c46a1019b test(ui): Update some tests about fuzzy matcher.
These test cases are more easy to understand than the previous one if
you ask me.
2023-08-09 14:15:10 +02:00
Ivan Enderlin 61f481c126 test(ui): Test RoomList::entries_with_dynamic_filter.
This patch updates the tests to ensure `entries_with_dynamic_filter`
works as expected.
2023-08-09 13:53:14 +02:00
Ivan Enderlin bd2f5119db feat(ui): Rename entries_filtered to entries_with_static_filter.
Because it makes more sense.
2023-08-09 13:52:55 +02:00
Jonas Platte 1b8768becc Use AsyncCell instead of mpsc channel 2023-08-09 13:24:39 +02:00
Damir Jelić 3d3021efc0 Expose bindings to manage dehydrated devices 2023-08-09 12:32:48 +02:00
Damir Jelić 64ca96543e Initial support for dehydrated devices
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-08-09 12:32:48 +02:00
Damir Jelić 8ce3b56180 Allow an Olm Account to generate its own device id 2023-08-09 12:32:48 +02:00
Damir Jelić 3e2f531caa Split out the receive_sync_changes method into a helper
This splits the method out so the bigger chunks doesn't persist the
changes in the store.

This will be useful if we need to hijack the changes and persist them in
a different store.
2023-08-09 12:32:48 +02:00
Jonas Platte 56f771a347 ui: Remove unnecessary boxing 2023-08-09 10:46:13 +02:00
Ivan Enderlin 1ab0d2081a doc(ui): Add inline comments. 2023-08-09 10:01:24 +02:00
Ivan Enderlin 90c30e4058 ui: Add RoomList::entries_with_dynamic_filter 2023-08-09 10:01:15 +02:00
Ivan Enderlin 752e814168 fix(ui): Add m.room.member: $LAZY in the require states of visible_rooms.
This patch tries to fix https://github.com/vector-im/element-x-
ios/issues/1204 by adding the following required states in the
`visible_rooms` sliding sync list: `m.room.member: $LAZY`.
2023-08-09 09:11:20 +02:00
Benjamin Bouvier 80c9464f1e chore: fix typo in cargo xtask kotlin doc comment (#2387) 2023-08-08 16:00:20 +02:00
Benjamin Bouvier 625acb056a feat(ffi): add an optional file layer for tracing (#2384)
* feat(ffi): add an optional file logger

Also makes logging to stdout/logcat optional.

* WIP: stupidly duplicate code 🤷

* ffi: Get rid of duplication in tracing initialization

* feat: add OtlpTracingConfiguration too

* feat: log either to stdout on non-android or logcat on android

---------

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-08-08 12:37:32 +00:00
Nicolas Mauri ff9923c6a6 Allow to define the default notification mode for a given room type (#2369)
* Add a function to define the default notification mode for a given kind of room

* Code refactoring

* sdk+ffi: code refactoring
2023-08-08 14:20:17 +02:00
Kévin Commaille 1680a7d4d6 sdk: Set the refresh token lock if refresh token is missing
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-08-08 12:16:03 +02:00
Kévin Commaille 302cff6ba9 sdk: Make RefreshTokenError authentication API-agnostic
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-08-08 12:16:03 +02:00
Ivan Enderlin dc3636d766 feat(ui): Turn on cached list for SlidingSync in RoomListService
feat(ui): Turn on cached list for `SlidingSync` in `RoomListService`
2023-08-08 11:29:51 +02:00
Ivan Enderlin 87f7fc7367 chore: Update Cargo.lock. 2023-08-07 17:48:17 +02:00
Ivan Enderlin 741bb1c12d chore: Update Cargo.lock. 2023-08-07 17:20:19 +02:00
Ivan Enderlin 7e371a642a feat(ui): Turn on cached list for SlidingSync in RoomListService.
Now is a good time to re-enable the cache :-).
2023-08-07 15:54:08 +02:00
Ivan Enderlin 1c01a146b1 chore: Update Cargo.lock. 2023-08-07 12:10:44 +02:00
Jonas Platte 074fd8b335 Upgrade opentelemetry crates 2023-08-06 12:02:41 +02:00
Benjamin Bouvier 5c74a597eb nit: prefer using assert_matches! in tests 2023-08-04 17:41:47 +02:00
Benjamin Bouvier dd3cab9409 fix: don't try /context when a notification has been filtered out 2023-08-04 17:41:47 +02:00
Benjamin Bouvier aea0b00ac2 chore: add comments to get_notification about the return type 2023-08-04 17:41:47 +02:00
Benjamin Bouvier e1c928f121 fix: compute whether a room is a DM before joining it
If we were invited and joined a room, then the computation of is_direct() was happening after
joining, and depended on the presence of a global account event of type "m.direct". This event
is added by the "set_is_direct()" call thereafter, so this couldn't ever happen.

This fixes it by computing the is_direct field *before* joining the room, and then marking the
room as a DM based on that.
2023-08-04 17:41:47 +02:00
Benjamin Bouvier 7676ff509f nit: remove else after return 2023-08-04 17:41:47 +02:00
Benjamin Bouvier 10f709450e tests(notification): add integration tests for notification events 2023-08-04 17:41:47 +02:00
Jonas Platte 4ebc3d70ad Hide test helpers from documentation 2023-08-03 21:39:56 +02:00
Jonas Platte 1679669376 Remove the 'testing' feature from matrix-sdk-ui
At the expense of slightly increasing our public API, but we can always
deprecate things.
2023-08-03 21:39:56 +02:00
Jonas Platte edc6428e69 Fix clippy warnings 2023-08-03 21:39:56 +02:00
Kévin Commaille a434b97c54 sdk: Don't derive (De)serialize for AuthSession
It turns out not all authentication API sessions (i.e. OIDC) are fully (de)serializable.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-08-03 20:47:30 +02:00
Ivan Enderlin 6c0f24b657 chore: Update UniFFI fork
chore: Update UniFFI fork
2023-08-03 15:42:30 +02:00
Ivan Enderlin 2481e1f97e chore: Update UniFFI fork. 2023-08-03 15:36:52 +02:00
Benjamin Bouvier bf754667d9 chore: get rid of Client::inherit_session
It does the same as `restore_session` now. Thanks @zecakeh for finding this out!
2023-08-03 14:10:32 +02:00
Nicolas Mauri 61d4d5b096 Fix an arithmetic overflow 2023-08-02 18:25:35 +02:00
Jonas Platte ea03b821fd ffi: Use UniFFI fork with future cancellation support
Upstream PR: https://github.com/mozilla/uniffi-rs/pull/1684
2023-08-02 16:16:24 +02:00
Jonas Platte f1b4b80460 ffi: Don't spawn tokio tasks for add_timeline_listener, send_attachment 2023-08-02 16:16:24 +02:00
Jonas Platte b39d06353a Revert "ffi: Spawn tokio tasks for the remaining async fns"
This reverts commit eb4dab138e.
2023-08-02 16:16:24 +02:00
Jonas Platte 34be01ecf2 Revert "ffi: Remove async from a few functions"
This reverts commit fc3883d08e.
2023-08-02 16:16:24 +02:00
Jonas Platte 0f6dd644b5 Upgrade async-rx 2023-08-01 17:31:29 +02:00
Jonas Platte 7d9d600eda ui: Only lock TimelineInnerState once per back-pagination response 2023-08-01 17:31:29 +02:00
Jonas Platte 8ac0f099f6 ui: Exclude Debug implementation from coverage 2023-08-01 17:31:29 +02:00
Jonas Platte 640e5fbacf ffi: Use batched timeline subscription 2023-08-01 17:31:29 +02:00
Jonas Platte 9a8c6249bb ui: Add Timeline::subscribe_batched 2023-08-01 17:31:29 +02:00
Jonas Platte a65cff01f7 ui: Create subscribe integration test sub-module 2023-08-01 17:31:29 +02:00
Benjamin Bouvier 80243615c4 chore(test): make the clear_with_echoes test more deterministic 2023-08-01 15:57:12 +02:00
Benjamin Bouvier a6aaf164aa chore(sliding sync): add logs for the limited flag computation
This should help us debug issues with false negatives.
2023-08-01 15:57:12 +02:00
Benjamin Bouvier ace282126f ffi(notifications): include full timeline event again in the NotificationEvent
The full timeline event contains both the timestamp (requested in #2361) and the sender id;
now the sender id for the invite can also be contained in there, minimizing the amount of copying we're doing in the
FFI code.

Fixes #2361.
2023-08-01 15:09:18 +02:00
Nicolas Mauri 6961a7fb36 Fix is_user_mention_enabled and is_room_mention_enabled (#2357)
* Fix is_user_mention_enabled, is_room_mention_enabled

* Fix: XXX comments in UnitTests

* Update comments

* UnitTests: code refactoring
2023-08-01 14:43:32 +02:00
Jonas Platte f6d4357d52 Update Cargo.lock 2023-08-01 10:59:40 +02:00
Jonas Platte d05bd4bf2d ui: Deduplicate in-reply-to fetching 2023-08-01 09:29:18 +02:00
Benjamin Bouvier a00ff293f1 chore: apply code review suggestions again 2023-07-31 15:02:04 +02:00
Benjamin Bouvier 505267503b chore: add a comment explaining why we need a mutex 2023-07-31 15:02:04 +02:00
Benjamin Bouvier c6f00cb633 chore(clippy): use an async mutex 2023-07-31 15:02:04 +02:00
Benjamin Bouvier 6cc4364684 chore: move comment around 2023-07-31 15:02:04 +02:00
Benjamin Bouvier d165ec8646 feat: sequentialize calls to get_notification_with_sliding_sync
There must be at most one sliding sync notification per connection id. While we could use multiple ids, it seems bad
to do so, in terms of proxy performance; so this makes it so that there is at most once notification being handled
at a time.
2023-07-31 15:02:04 +02:00
Benjamin Bouvier 30a662fdc8 chore: slim down the enum variants of NotificationEvent according to popular request 2023-07-31 15:02:04 +02:00
Benjamin Bouvier 9126937901 feat: use /context query if sliding sync failed 2023-07-31 15:02:04 +02:00
Benjamin Bouvier dec5c6bc2d feat(sliding sync): save the to-device token in OlmMachine::receive_sync_changes 2023-07-31 12:24:55 +02:00
Benjamin Bouvier 719683d284 feat: allow storing a next_batch_token in the crypto stores 2023-07-31 12:24:55 +02:00
Benjamin Bouvier e3f7b9676d chore: fix Changes::is_empty() 2023-07-31 12:24:55 +02:00
Benjamin Bouvier 745bb09e38 chore: remove unused token in KeysQueryRequest 2023-07-31 12:24:55 +02:00
Benjamin Bouvier 4c4a77da34 chore: group all the arguments to receive_sync_changes in a single struct 2023-07-31 12:24:55 +02:00
Jonas Platte 33629833bd ui: Keep local echoes when clearing timeline
This is especially important for ones that failed to send (or ones that
fail post timeline clearing), since there is no way to retry them
otherwise.
2023-07-31 12:12:35 +02:00
Benjamin Bouvier 4c7e10cbf3 chore: add messages for base-client processing 2023-07-31 11:31:12 +02:00
Benjamin Bouvier dcfea138ea chore: tweak sliding sync tracing context
- include the conn_id at the stream level, not the sync_once level (that's a child scope of the stream)\
- also include whether e2ee is enabled at this level
- shorten most static logs
- remove duplicate "sending request" logs
2023-07-31 11:31:12 +02:00
Benjamin Bouvier eb7e01e8b1 chore: lower log-levels when processing sliding sync in sdk-base 2023-07-31 11:31:12 +02:00
Damir Jelić c22f6da909 Add a js feature to the qrcode crate 2023-07-31 10:08:53 +02:00
Damir Jelić 4eba72774f Bump vodozemac 2023-07-31 10:08:53 +02:00
Jonas Platte d149782e6a ui: Reuse ID when replacing UTD after retrying decryption 2023-07-31 09:53:58 +02:00
Jonas Platte 0c3d46eb1e ui: Move some functions into new timeline::util module 2023-07-31 09:53:58 +02:00
Jonas Platte 7dfe8cedbb ui: Move timeline item creation helpers into TimelineInnerState 2023-07-31 09:53:58 +02:00
Jonas Platte e5f021f5cf ui: Simplify definition of TimelineEventHandler
Take a mutable reference to TimelineInnerState, instead of individual
mutable references to its fields.
2023-07-31 09:53:58 +02:00
Jonas Platte 05c5fdd839 ui: Move Flow into EventHandlerContext 2023-07-31 09:53:58 +02:00
Jonas Platte c15ac3978e ui: Rename EventHandlerMetadata to EventHandlerContext 2023-07-31 09:53:58 +02:00
Jonas Platte 4b2cc20c6b ui: Move TimelineInner::*_internal to TimelineInnerState 2023-07-31 09:53:58 +02:00
Jonas Platte 18590c8813 ui: Move update_timeline_reaction to TimelineInnerState 2023-07-31 09:53:58 +02:00
Jonas Platte b78abd62c0 ui: Move TimelineInnerState into its own module 2023-07-31 09:53:58 +02:00
Jonas Platte b425812fd8 ui: Remove explicit default generic argument 2023-07-31 09:53:58 +02:00
Benjamin Bouvier f57b6f5491 chore(test): don't use the same database name in multiple independent tests
This should make the test less flaky.
2023-07-28 19:17:01 +02:00
Benjamin Bouvier 18e283fee9 chore: add TODO explaining that this field should be put back into the OlmMachine later 2023-07-28 15:21:12 +02:00
Benjamin Bouvier a3291a3627 fix: store the crypto store known generation in the Client, not the OlmMachine 2023-07-28 15:21:12 +02:00
Benjamin Bouvier deb8434ee9 test: add a regression test for the spurious crypto store regeneration 2023-07-28 15:21:12 +02:00
Damir Jelić 63ce957843 fix: don't rely on having sessions in cache priori to encrypting a message 2023-07-28 15:06:12 +02:00
Benjamin Bouvier 819b46f2fb test: add regression test for invalidating the OlmMachine while sending a message 2023-07-28 15:06:12 +02:00
Marco Romano 5c71087b9c Upgrade Ruma
This is to pave the way to the upcoming "polls" work.
2023-07-28 08:38:50 +00:00
Jonas Platte d865665420 ffi: Replace tracing-android by paranoid-android 2023-07-27 18:25:15 +02:00
Jonas Platte 5f5028ddd0 ffi: Remove redundant dependency specification 2023-07-27 18:25:15 +02:00
Jonas Platte 8703fea24f ffi: Simplify OTLP tracing initialization 2023-07-27 18:25:15 +02:00
Benjamin Bouvier ab363aa420 chore: slim down UpdateSummary 2023-07-27 17:21:36 +02:00
Jonas Platte b66cd58a60 ui: Reduce work for updating sender profiles
… we should not be fetching the profile if it's already set.
2023-07-27 16:45:57 +02:00
Ivan Enderlin 7892b8b74a feat(sdk+ui+ffi): Implement (SlidingSyncRoom|Room|RoomListItem)::avatar_url
feat(sdk+ui+ffi): Implement `(SlidingSyncRoom|Room|RoomListItem)::avatar_url`
2023-07-27 16:33:33 +02:00
Jonas Platte fc3883d08e ffi: Remove async from a few functions
… to hopefully work around current issues.
2023-07-27 12:37:02 +02:00
Ivan Enderlin c2a8fbd3c9 feat(ui): Implement the “fuzzy match room name” filter
feat(ui): Implement the “fuzzy match room name” filter
2023-07-27 12:32:47 +02:00
Andrew Ferrazzutti 53668d764c Return a KeysBackupRequest instead of the more generic OutgoingRequest 2023-07-27 12:12:08 +02:00
Ivan Enderlin 936a2ee25b chore(sdk): Remove one TODO in Sliding Sync
chore(sdk): Remove one `TODO` in Sliding Sync
2023-07-27 10:37:45 +02:00
Benjamin Bouvier 1f10af9fa9 chore(encryption): replace some AuthenticationRequired with NoOlmMachine
Even if the two issues are likely caused by the same root cause (the user not being
authenticated), they should be used in different contexts, in my understanding:

- the authentication required error should happen only during HTTP requests
- the absence of olm machine should be signalled when we try to get one and it's not been initialized yet

This has caused a bit of confusion when looking at debug traces today, so this patches fixes it.
2023-07-27 10:36:57 +02:00
Ivan Enderlin 38a8ad0a66 chore(ci): Make typos happy. 2023-07-27 10:10:45 +02:00
Ivan Enderlin d1d6bcdcab doc(ui): Add missing documentation. 2023-07-27 10:10:45 +02:00
Ivan Enderlin 3e93bdbc3f feat(ui): Normallize strings when doing fuzzy matching. 2023-07-27 09:20:43 +02:00
Ivan Enderlin 63ca82c66c feat(ui): Implement the “fuzzy match room name” filter.
WIP
2023-07-26 18:22:14 +02:00
Ivan Enderlin 0c16ff1ae7 chore(cargo): Update ruma. 2023-07-26 16:38:23 +02:00
Ivan Enderlin a8e5d3ab17 feat(ffi): Update RoomListItem::avatar_url.
This patch updates `RoomListItem::avatar_url` to use
`matrix_sdk_ui::room_list_service::Room::avatar_url` instead of
`matrix_sdk::Room::avatar_url`.

This patch also moves `avatar_url` before `is_direct` (so that it's the
same order as other places in the code).
2023-07-26 16:38:23 +02:00
Chris Smith af44ac6be4 ffi: Define API stub for m.poll.start and m.poll.end 2023-07-26 14:26:03 +00:00
Ivan Enderlin 38d28e9aa0 feat(ui): Implement Room::avatar_url.
This patch implements `Room::avatar_url`. It tries to calculate
the best avatar URL as much as possible. It's either the URL from
`SlidingSyncRoom::avatar_url` or from `Room::avatar_url`.
2023-07-26 16:06:11 +02:00
Ivan Enderlin 62a203f41e feat(sdk): Implement SlidingSyncRoom::avatar_url.
Based on https://github.com/ruma/ruma/pull/1607, this patch adds
support for `avatar` from a sliding sync response. This patch implements
`SlidingSyncRoom::avatar_url` to get the avatar URL of a sliding sync
room.
2023-07-26 16:06:11 +02:00
Ivan Enderlin 15a6861b8c chore(sdk): Remove one TODO.
The batch subscriber exists in `matrix_sdk_ui::RoomList` (https://
github.com/matrix-org/matrix-rust-sdk/pull/2322) instead of being added
here.

We must keep the observable capacity to 4096, but the `TODO` is no
longer relevant.

Why keeping the capacity to 4096? Because if the batch subscriber (in
`RoomList`) isn't “listened” quickly, we don't want to get a `Reset`.
2023-07-26 15:24:24 +02:00
Ivan Enderlin 163d8ca517 feat(ui): Batch the streams returned by RoomList::entries and RoomList::entries_filtered.
feat(ui): Batch the streams returned by `RoomList::entries` and `RoomList::entries_filtered`.
2023-07-26 15:18:43 +02:00
Ivan Enderlin efd1d1e9d2 chore(cargo): Use latest version of async-rx. 2023-07-26 14:25:50 +02:00
Ivan Enderlin 8a21a8a6da chore(ui): Address some feedbacks. 2023-07-26 14:19:17 +02:00
Ivan Enderlin 2302a7b377 feat(ui): Use Subscriber<State> to simply drain the batch subscriber for entries.
This patch brings a nice code simplification.

Instead of creating a new `Stream` with `tokio` based on
`Subscriber<State>`` to drain the batch subscriber for
`RoomList::entries` and `::filtered_entries`, we can _simply_ use
`Subscriber<State>` directly! It removes one dependency: `tokio-
stream`, and remove possible issues with the broadcast channel
`tokio::sync::broadcast`. The code is much simpler and straighforward.
2023-07-26 14:07:46 +02:00
Benjamin Bouvier 7469d1c7d8 chore: remove all experimental features on the UI crate
As discussed, we think the entire UI crate should be considered experimental. We've also
observed a proliferation of feature flags there (many of those are my wrong doings, sorry).
Since the one internal user (FFI) of that crate enabled all experimental features, it seems
fine to make them all default, while not providing any extra stability guarantee based on that
action.
2023-07-26 13:35:40 +02:00
Benjamin Bouvier 2cec3b0c45 feat: Run a SlidingSync when retrieving notifications to get more information (#2252)
* feat: run a room sliding sync upon receiving a notification, to get its full content

* test: add test for the new sliding-sync in notifications \o/

* fix: try to get the push rules *after* a possibly-successful event decryption

* feat: set `is_noisy` only if we could build a push context, and test it

* feat: expose the `legacy_get_notification` in the FFI layer

* feat: retrieve events with a `/context` query

* fix: also request the client's user id's member information to get their display name

* feat: include the list of invites in the notification sliding sync

* feat: repeat the query multiple times if the event hasn't been immediately found

* chore: simplify retrying decryption

* chore: fail the sliding sync when fetching a notification if not using a memory store

* chore: update test expectations + sort invites by recency

* chore: cargo fmt

* chore: simplify getting push actions

Either they were already available in the timeline event if we had to re-run decryption, or
we manually compute them. (Previous comment was incorrect, the `push_actions` are now optional
because of another PR of yours truly.)

* fixup! chore: fail the sliding sync when fetching a notification if not using a memory store

* feat: try to handle invites correctly

* chore: build a local client with an in-memory store instead of reusing the parent client

* fix: remove dubious annotation

* feat: allow cloning a Client and modify it on the fly, use that for the notification client

* feat: get rid of the with_memory_state_store public func on ClientBuilder

* feat: include sender_id in the notification invite event

* feat: put sender's id in the `NotificationSenderInfo`

* feat: inherit the parent session when creating a notification client

* chore: reformat comments

* TMP: add logs

* chore: regenerate the olm machine when inheriting a session too

* chore: keep the parent client around for legacy_get_notification/with_context
2023-07-25 17:23:01 +00:00
Benjamin Bouvier 483465e8a8 feat(sliding sync): include the connection id conn_id in the tracing context 2023-07-25 18:49:37 +02:00
Benjamin Bouvier bef9cbfacc feat: rename Protocol to Scheme + replace server_name_with_protocol by insecure_server_name_no_tls 2023-07-25 18:43:35 +02:00
Benjamin Bouvier 122349ecec feat: allow specifying a protocol along a server name 2023-07-25 18:43:35 +02:00
Benjamin Bouvier 020d5aa292 fix: use a little state machine to handle the response
and that allows to make sure that all event handlers are correctly called for all
events contained in the response.
2023-07-25 17:36:43 +02:00
Benjamin Bouvier fb5c96e380 test: add test for previous feature 2023-07-25 17:36:43 +02:00
Benjamin Bouvier 8a809dba04 feat(sliding sync): only process encryption events (resp. room events) if configured as so 2023-07-25 17:36:43 +02:00
Benjamin Bouvier da7d1b092e chore(encryption sync): lower severity of update summary logging
The proxy server can (and does) redispatch unrelated lists/rooms from other sliding sync connections,
so the encryption sync would sometimes see room events. Apparently since this is not considered a bug
in the SS proxy, so we shouldn't spam error traces every time this happens.
2023-07-25 17:36:43 +02:00
Jonas Platte 04aac7bc13 ui: Redact replied-to event inside in_reply_to in timeline 2023-07-25 15:31:14 +02:00
Jonas Platte 004a4aa765 ui: Fix redactions for state events in the timeline 2023-07-25 15:31:14 +02:00
Jonas Platte 09fea85d92 ui: Replace Message by TimelineItemContent in RepliedToEvent 2023-07-25 15:31:14 +02:00
Jonas Platte d93f4ee4cc Fix clippy lint 2023-07-25 15:31:14 +02:00
Jonas Platte eb4dab138e ffi: Spawn tokio tasks for the remaining async fns
… as a workaround for cancellation being broken in UniFFI.
2023-07-25 14:34:31 +02:00
Damir Jelić 0a361eff5c Mark our public part of the user identity as verified if we import the private part (#2298)
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-07-25 12:39:06 +02:00
Jonas Platte bf26a343da ffi: Spawn tokio tasks for async code that locks async mutexes
Since we seem to have a problem with futures being leaked in Kotlin,
which can otherwise lead to deadlocks.
2023-07-24 20:01:27 +02:00
Ivan Enderlin d60a65f82b chore: Make Clippy happy. 2023-07-24 16:59:46 +02:00
Ivan Enderlin d39cbd865b feat(ffi): RoomListEntriesListener::on_update takes a Vec<_> now.
Since `RoomList::entries` returns a batch stream, the listener
now receives a `Vec<VectorDiff<RoomListEntry>>` instead of a
`VectorDiff<RoomListEntry>`.
2023-07-24 16:53:26 +02:00
Ivan Enderlin 3bcd9680fd test(ui): Test the new batch stream on entries and filtered entries.
Only updated the macro is required here. Instead of calling
`StreamExt::now_or_never` on the `$stream`, we call `Iterator::next` on
`$entries` which is a `Vec<VectorDiff<_>>` now.
2023-07-24 16:53:26 +02:00
Ivan Enderlin 1678d0754d feat(ui): Batch the stream returned by RoomList::entries and RoomList::entries_filtered.
This patch uses a newly implemented `async-rx` crate, that provides
`StreamExt`. This trait provides new features on `Stream`, like
`StreamExt::batch_with` which allows to batch values generated by a
`Stream` into a `Vec<Stream::Item>`. The batch is drained based on
another `Stream`: every time a value is produced, it drains the batch
stream.

This feature is used in `RoomList::entries` and
`RoomList::entries_filtered` to batch `Stream<Item = VectorDiff<_>>`
into `Stream<Item = Vec<VectorDiff<_>>>`.

The “drainer” is a broadcast sender, which sends an (empty) value every
time the room list service state changes, so every time something
happens during a sync. Note that it even drains when the room list
service state jumps to `Error` or `Terminated`.
2023-07-24 16:53:26 +02:00
Ivan Enderlin ac950ac253 doc(sdk): Update documentation of RoomListEntry variants. 2023-07-24 15:12:30 +02:00
Ivan Enderlin 2e265ad9bd feat(ui): Look for Room in the ring buffer from the newest instead of the oldest
feat(ui): Look for `Room` in the ring buffer from the newest instead of the oldest
2023-07-24 15:03:53 +02:00
Jonas Platte 766be8142e ffi: Make fetch_members cancellable 2023-07-24 13:59:09 +02:00
Ivan Enderlin 5dbfa89c72 feat(ui): Look for Room in the ring buffer from the newest instead of the oldest.
In `RoomListService`, there is a cache over the `Room`s to avoid re-
computing them every time the user calls `RoomListService::room`. It
also allows async operations to have time to finish while the user
jumps back to the room list and so on.

When reading this cache, which is `matrix_sdk_common::RingBuffer`,
`RingBuffer::iter` is used with `Iter::find` to find if the
`Room` exists in the cache. The cache has a size of 128 (given by
`ROOM_OBJECT_CACHE_SIZE`), which is quite large.

`Iter::find` will iterate from the oldest to the newest room in the
cache, but realistically, I reckon we need to iterate from the newest
to the oldest room. Indeed, the app user is more likely to jump between
recently opened rooms more frequently, thus saving quite a lot of
iterations for usual cases.

One may argue than in practice, a user won't open 128 rooms anyway… :-p.
2023-07-24 09:34:36 +02:00
Jonas Platte d9f3a5476e ffi: Simplify regular (non-OTLP) tracing initialization 2023-07-21 13:59:40 +02:00
Jonas Platte 0e3cca09dd ffi: Remove duplicate dependency 2023-07-21 13:59:40 +02:00
Jonas Platte 230cacaf18 ui: Add more tracing for room update handling 2023-07-21 11:06:31 +02:00
Jonas Platte 5b639903bb ui: Add more tracing for updating sender profiles 2023-07-21 11:06:31 +02:00
Jonas Platte abab1a32aa ui: Fix a typo 2023-07-21 11:06:31 +02:00
Jonas Platte 7e1f8f4923 base: Add tracing for getting a room member 2023-07-21 11:06:31 +02:00
Jonas Platte 236f6fff88 Make example dependencies less confusing 2023-07-20 13:28:14 +02:00
Jonas Platte 80fb8ff283 ffi: Don't keep timeline locked while back-paginating 2023-07-20 12:08:41 +02:00
Richard van der Hoff c6dab4088e Rename "Recovery Key" to "Backup decryption key" (#2305)
... because there are too many "recovery keys"
2023-07-20 09:49:54 +01:00
Damir Jelić 24e3ccfbb3 Add stronger typing to the return value of receive_sync_changes in the bindings (#2297) 2023-07-19 12:43:41 +00:00
Damir Jelić 9f69c32467 Broadcast valid secrets we receive over m.secret.send
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-07-19 14:27:18 +02:00
Damir Jelić 6ea0d886f7 Introduce a secret inbox
Up until now, users had to listen for to-device events to check for
secrets that were received as an `m.secret.send` event.

This has a bunch of shortcomings:
    1. Once the has been given to the consumer, it's gone and can't be
       retrieved anymore. Secrets may get lost if an app restart happens
       before the consumer decides what to do with it.
    2. The consumer can't be sure if the event was received in a
       secure manner.

This commit ads a inbox for our received secrets where we will long-term
store all secrets we receive until the user decides to delete them.

It's deemed fine to store all secrets, since we only accept secrets we
have requested and if they have been received from a verified device of
ours.
2023-07-19 14:27:18 +02:00
Jonas Platte 689d022b7a ui: Group timeline reaction metadata into a new struct 2023-07-19 14:01:32 +02:00
Jonas Platte 340e0b7a03 ci: Use taiki-e/install-action to install protoc 2023-07-19 10:11:21 +02:00
Jonas Platte 38a2885c10 Bump eyeball-im, enable its tracing feature 2023-07-18 16:40:47 +02:00
Jonas Platte aaa84c1489 Ensure valid room state before sending requests from Room methods 2023-07-18 15:12:03 +02:00
Jonas Platte 293cd08634 Clean up double spaces in docs 2023-07-18 15:12:03 +02:00
Jonas Platte e3ba1f1cb8 Add room API changes to changelog 2023-07-18 15:12:03 +02:00
Jonas Platte f3da79e482 Split unreleased section of changelog into sub-sections 2023-07-18 15:12:03 +02:00
Jonas Platte 92df7b22ec Rename room::Common to Room
… and export it at the matrix_sdk crate root.
2023-07-18 15:12:03 +02:00
Jonas Platte db84fcd8da Merge matrix_sdk::room::common into its parent module 2023-07-18 15:12:03 +02:00
Jonas Platte 67ef9c3fa0 Remove Room enum 2023-07-18 15:12:03 +02:00
Jonas Platte 85f66b1f96 Remove room::Joined 2023-07-18 15:12:03 +02:00
Jonas Platte fbea71aaf5 Move room::Join methods to room::Common 2023-07-18 15:12:03 +02:00
Jonas Platte 3602807641 Remove invalid reference in docs 2023-07-18 15:12:03 +02:00
Jonas Platte 6ad8086905 Move get_messages related types into a new room submodule 2023-07-18 15:12:03 +02:00
Jonas Platte 0408bd5a98 Remove room::Left 2023-07-18 15:12:03 +02:00
Jonas Platte ef00ef4158 Remove room::Joined::leave
… since room::Common::leave is now public and Joined deref's to Common.
2023-07-18 15:12:03 +02:00
Jonas Platte 9c8ed429b9 Move room::Left::forget to room::Common 2023-07-18 15:12:03 +02:00
Jonas Platte 05a817305e Remove room::Invited 2023-07-18 15:12:03 +02:00
Jonas Platte 10339ca15b Remove unreachable error variant 2023-07-18 15:12:03 +02:00
Jonas Platte 4621a47304 Move invite_details from room::Invited to room::Common 2023-07-18 15:12:03 +02:00
Jonas Platte 512dc18250 Remove room::Invited::accept_invitation in favor of room::Common::join 2023-07-18 15:12:03 +02:00
Jonas Platte 677907f8c5 Remove room::Invited::reject_invitation in favor of room::Common::leave 2023-07-18 15:12:03 +02:00
Jonas Platte 67abf707ff ui: Move ReactionSenderData to reactions module and re-export publically 2023-07-18 15:05:12 +02:00
Jonas Platte 145c796294 crypto: Remove Arc from fields of types that are no longer Clone 2023-07-18 14:58:11 +02:00
Jonas Platte a1806254a8 crypto: Remove Clone from more store related types 2023-07-18 14:58:11 +02:00
Jonas Platte 7081c67c46 Remove Arc wrapping from memory store fields 2023-07-18 14:58:11 +02:00
Jonas Platte 10d9808ecf Remove dead code 2023-07-18 14:58:11 +02:00
Jonas Platte 1c26069871 Make memory stores not clonable
It's not actually necessary and allows simplifying them.
2023-07-18 14:58:11 +02:00
Benjamin Bouvier 2f2bb82fcf add tests for the previous commit 2023-07-18 14:29:58 +02:00
Benjamin Bouvier e3cac2b637 fix: Don't mark a room as limited if the SS response doesn't contain any events 2023-07-18 14:29:58 +02:00
Jonas Platte 82bbb59965 ui: Update dedup_initial test to also test ID reuse
… and make it more realistic.
2023-07-18 14:20:29 +02:00
Jonas Platte 2720bddcc4 ui: Reuse IDs when removing and re-inserting an item 2023-07-18 14:20:29 +02:00
Jonas Platte ab0d88ae3a ui: Pull out identical code in different branches 2023-07-18 14:20:29 +02:00
Jonas Platte 78ebb7f745 ui: Remove redundant semicolon 2023-07-18 14:20:29 +02:00
Jorge Martin Espinosa 26edf4169f ffi: Use forked tracing-android temporarily
… to have readable logs in Android.
2023-07-17 15:48:56 +00:00
Benjamin Bouvier 4ad95ab1a1 chore: rename SyncService::observe_state to state 2023-07-17 16:46:07 +02:00
Benjamin Bouvier 2b3b5e9e18 feat: introduce Idle initial state 2023-07-17 16:46:07 +02:00
Benjamin Bouvier f1d67ad593 chore: remove RoomListService::is_syncing
It's preferrable that users make use of the `App::observe_state/current_state` methods, instead
as there's another sliding sync in the `App` and it properly unifies the current state of both.
2023-07-17 16:46:07 +02:00
Benjamin Bouvier 766d787779 chore: don't automatically start the App 2023-07-17 16:46:07 +02:00
Benjamin Bouvier 86ef0d41f8 feat: add getter to get the current state of the App 2023-07-17 16:46:07 +02:00
Jonas Platte a503dccdcd base: Apply redaction to latest_event in RoomInfo when applicable 2023-07-17 16:36:37 +02:00
Jonas Platte 0bb7a9de7f base: Remove wrong documentation 2023-07-17 16:36:37 +02:00
Jonas Platte 8067319208 base: Add BaseRoomInfo::room_version() 2023-07-17 16:36:37 +02:00
Jonas Platte 70421cff54 base: Restrict visibility of RoomInfo#latest_event 2023-07-17 16:36:37 +02:00
Benjamin Bouvier 68a5f70a85 chore: rename App to SyncService 2023-07-17 14:38:38 +02:00
Benjamin Bouvier a939522b1b test: add test for computing the limited flag 2023-07-17 13:51:45 +02:00
Benjamin Bouvier b3183f6315 feat: compute limited approximation client-side 2023-07-17 13:51:45 +02:00
Benjamin Bouvier 2357a09d13 chore: remove spurious feature guarding against "experimental-sliding-sync"
The whole file is guarded against this feature.
2023-07-17 13:51:45 +02:00
Mauro 91ea0ee8b1 Add PowerLevels permission checks to room::Joined
… and the FFI `Room`. These were previously already available at the `room::Member` level.
2023-07-17 11:43:31 +00:00
Benjamin Bouvier d98b296fad feat: reflect that push actions may not be available for a TimelineEvent
Before this patch, the inability to compute push actions would result in an empty vector of
push actions, leading to the false assumption that there's no push actions to account for,
while we were unable to compute them properly. This changes things up so that it's now the
user's responsibility to decide what to do when those push actions are missing.
2023-07-17 13:38:33 +02:00
Kévin Commaille 1dea482e43 ui: Expose the kind of a TimelineItem
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-07-14 19:17:17 +02:00
Kévin Commaille a6a0d722b4 ui: Fix imports behind sliding-sync feature flag
Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-07-14 13:05:39 +02:00
Jonas Platte d82628725f ci: Remove serverName from setup-matrix-synapse arguments
We're getting warnings because this is not a supported parameter.
2023-07-14 12:05:02 +02:00
Benjamin Bouvier ac51adf1e5 feat: split the SlidingSyncBuilder with_timeouts method into two
Following a comment from Jonas in another PR.
2023-07-14 11:29:26 +02:00
Jonas Platte bfed0907ed Remove wasm_command_bot 2023-07-14 11:07:47 +02:00
Jonas Platte b2f7ba33c8 Patch const_panic to not use packed reference 2023-07-14 10:46:52 +02:00
Jonas Platte 6d8764f4e7 Patch const_panic 2023-07-14 10:46:52 +02:00
Jonas Platte 57c5347e81 ffi: Move more types out of UDL 2023-07-14 10:46:52 +02:00
Jonas Platte 9cf1adbfa6 Upgrade UniFFI 2023-07-14 10:46:52 +02:00
Dirk Stolle 0141dab8c9 ci: Update actions/checkout in GitHub Actions workflows to v3
Signed-off-by: Dirk Stolle <striezel-dev@web.de>
2023-07-14 10:22:55 +02:00
Dirk Stolle 0461ebe739 Fix some typos
Signed-off-by: Dirk Stolle <striezel-dev@web.de>
2023-07-14 10:10:03 +02:00
Jonas Platte 81d28d1f46 ui: Remove read-only mode for timeline
It was used when getting the latest message was done through the timeline,
which is no longer the case.
2023-07-13 15:48:56 +02:00
Jonas Platte d309cb3320 crypto-ffi: Use proc-macro definition of callback interfaces 2023-07-13 14:07:27 +02:00
Jonas Platte 3c2b2756b0 ci: Fix git ref comparison 2023-07-13 13:54:59 +02:00
Jonas Platte 5f924197fd ffi: Merge impl blocks for sync and async exported methods 2023-07-13 13:13:50 +02:00
Jonas Platte aee2ef6abf ffi: Use async-uniffi instead of block_on in app module 2023-07-13 13:13:50 +02:00
Jonas Platte 6f2fee8965 Upgrade UniFFI 2023-07-13 13:13:50 +02:00
Jonas Platte 5abc781e1f Remove matrix-sdk-crypto-js
It now lives in its own repository at
https://github.com/matrix-org/matrix-rust-sdk-crypto-web
2023-07-13 13:11:02 +02:00
Richard van der Hoff 51ba4483c0 Merge pull request #2270 from matrix-org/release-matrix-sdk-crypto-js-0.1.4
matrix-sdk-crypto-js v0.1.4
2023-07-12 17:24:16 +01:00
Richard van der Hoff b62bb90c79 matrix-sdk-crypto-js v0.1.4
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 31s
2023-07-12 16:58:19 +01:00
Richard van der Hoff a4cece7dd7 crypto: Add OlmMachine::query_keys_for_users (#2267)
Sometimes we need our key query results to be as up-to-date as possible. Add a mechanism to allow that.

Closes #2263 .
2023-07-12 16:48:34 +01:00
Jonas Platte ec34036586 ci: Switch to branch-less GitHub pages workflow 2023-07-12 17:47:04 +02:00
Jonas Platte 6e10eb9efb Remove matrix-sdk-crypto-js
It now lives in its own repository at
https://github.com/matrix-org/matrix-rust-sdk-crypto-nodejs
2023-07-12 16:23:37 +02:00
Jonas Platte a554a92dec crypto: Re-export vodozemac entirely 2023-07-12 15:14:47 +02:00
Jonas Platte ccb6d7d05c Move ruma re-export from matrix-sdk to matrix-sdk-common
… since it's also useful for crypto bindings.
2023-07-12 15:14:47 +02:00
Jonas Platte f6c339a5d2 Enable ruma's new compat-upload-signatures feature 2023-07-12 12:42:31 +02:00
Jonas Platte 63babcd35a Upgrade Ruma 2023-07-12 12:42:31 +02:00
Jonas Platte 8c3af12e47 test: Rename EventBuilder to SyncResponseBuilder
… because that's what it is.
2023-07-12 10:29:29 +02:00
Richard van der Hoff e1c5f628e7 Merge pull request #2258 from matrix-org/matrix-sdk-crypto-js-0.1.3
Fix `receiveSyncChanges` js bindings, and release 0.1.3
2023-07-11 18:12:16 +01:00
Richard van der Hoff f1def2a458 fix another test
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 30s
2023-07-11 17:19:58 +01:00
Richard van der Hoff 0bd1e65b49 update changelogs 2023-07-11 16:41:45 +01:00
Richard van der Hoff fd3c4f669d matrix-sdk-crypto-js v0.1.3 2023-07-11 16:41:22 +01:00
Richard van der Hoff a3a36291ad crypto-js Fix return type of receiveSyncChanges
https://github.com/matrix-org/matrix-rust-sdk/pull/2142 introduced an
unintended change such that `receiveSyncChanges` returned an array of arrays.
2023-07-11 16:29:17 +01:00
Jonas Platte 0daf3aeb6b ui: Move echoes to the bottom immediately when retrying 2023-07-11 16:36:26 +02:00
Jonas Platte 9dc6ac45d2 ui: Fix wrong field name 2023-07-11 16:36:26 +02:00
Jonas Platte 53ac8bea14 ffi: Provide tokio context for async exported method 2023-07-11 15:54:38 +02:00
Jonas Platte 01ceec43b1 ui: Use eyeball_im's new entry API 2023-07-11 15:41:34 +02:00
Benjamin Bouvier 43bbd4200e chore: inline variable and use From<bool> for WithLocking 2023-07-11 14:07:27 +02:00
Benjamin Bouvier 2d9f7d2f89 ffi: remove bindings for manually creating EncryptionSync and RoomList 2023-07-11 14:07:27 +02:00
Benjamin Bouvier dbc8f0136b ffi: add bindings for App 2023-07-11 14:07:27 +02:00
Benjamin Bouvier dd7d4ddf7c chore: rebase 2023-07-11 14:07:27 +02:00
Benjamin Bouvier e60d04b368 doc: document the App API 2023-07-11 14:07:27 +02:00
Benjamin Bouvier 704cd7784b feat: experimental App API that wraps both the encryption sync and room list API 2023-07-11 14:07:27 +02:00
Damir Jelić 1c6d85935d Generate one-time keys when we receive new one-time key counts (#2249)
Previously we would generate one-time keys, if needed, whenever we tried
to upload them. This code-path is critically missing a `save_account()`
call and we would only persist the account once we uploaded the one-time
keys.

This patch changes things up to generate one-time keys whenever we
receive new one-time key counts from the sync response. This aligns
with the way we generate fallback keys and removes the need to introduce
a new place where we persist the `Account`.

It's still possible to re-upload the same one-time keys, in the case
where the upload process succeeds on the server side but we fail to
receive the response.

Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2023-07-11 10:27:04 +00:00
Benjamin Bouvier 9985a63dd3 feat: use a ring buffer for the RoomListService cache
This will limit the memory used by the cache entries (while it was unbounded before). It's now possible to do this,
since we have the `latest_room_event` handy for all the rooms; using the unbounded cache before was papering over
the lack of that feature.

We can bikeshed on the number of entries in this cache. It has to be small enough to not blow up memory (and keep
the linear search over room id fast, but it's secondary), and high enough that we don't hit the full timeline
re-build path that often.
2023-07-11 10:57:04 +02:00
Richard van der Hoff 00e85ef275 Disable debug logging for tests
Because https://github.com/jestjs/jest/issues/4156 is closed
2023-07-11 10:56:42 +02:00
Jonas Platte 879d1b189c ui: Test unique ID tracking 2023-07-10 19:19:31 +02:00
Jonas Platte a11508fa58 ui: Linkify doc bits 2023-07-10 19:19:31 +02:00
Jonas Platte 754b668fe6 ui: Move TimelineItem, TimelineItemKind into new sub-module 2023-07-10 19:19:31 +02:00
Mauro Romito bc811cc1dc ui: Add a unique ID for all timeline items
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-07-10 19:19:31 +02:00
Benjamin Bouvier 2acc13ed2c feat: add a new NotificationClient API 📬 (#2235)
* chore: rename EncryptionSyncMode variants

* feat: split the encryption sync modes into two different functions

* feat: make locking optional in the `EncryptionSync`

* feat: experimental notification client that retries decryption if it failed the first time

* fix: don't iloop retrying decryption

* chore: helper to convert from bool to `WithLocking`

* feat: don't loop and just retry decryption of the notification event linearly

* feat: remove unused set_notification_delegate

Dead code is dead.

* ffi: get rid of `get_notification_item` and introduce the `NotificationClient`

* fmt

* feat: don't swallow encryption sync errors when retrying notification event decryption

* keeping a tidy commit history is NP-hard

* will i ever learn

* chore: enable experimental-notification-client in the FFI crate

* test: add basic integration test for the common path

* Address first batch of review comments, thanks Jonas!
2023-07-10 18:06:13 +02:00
Vladimir Panteleev a094e10b35 base: Add unit test for deserialization failure of join events 2023-07-10 15:59:21 +02:00
Vladimir Panteleev ab781bf5f4 base: Fix deserialization failure leading to database corruption
f36a5b8cd7 introduced
deserialize_events, moving the deserialization out of handle_state.
However, the new code in deserialize_events used filter_map, which
caused a deserialization failure to lead to the indices of the
serialized and deserialized events to no longer match up.

This was not caught because iter::zip also does not require that its
arguments have matching lengths, causing the mismatch to cascade for
that batch of events, and persist in the store.  An application
affected by this form of corruption can, for example, call
room.get_state_events requesting events of a certain type, and getting
back events of a different type.

Fix the bug by using Option to preserve the length of
deserialize_events' return value, and add an assertion to ensure
handle_state's contract.

Signed-off-by: Vladimir Panteleev <git@cy.md>
2023-07-10 15:59:21 +02:00
Jonas Platte 6ea806bf18 base: Fix clippy lints 2023-07-10 14:03:58 +02:00
Richard van der Hoff 57ea8173ae Merge pull request #2248 from matrix-org/release-matrix-sdk-crypto-js-0.1.2
matrix-sdk-crypto-js v0.1.2
2023-07-10 12:27:10 +01:00
Richard van der Hoff bf1595309c matrix-sdk-crypto-js v0.1.2
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 30s
2023-07-10 12:03:32 +01:00
Richard van der Hoff caa70db31b Prepare changelog for v0.1.2 2023-07-10 12:03:12 +01:00
aringenbach 89aa2916c7 ui/ffi: Add timestamp to reactions 2023-07-10 10:13:53 +00:00
Benjamin Bouvier 2225e8ad80 feat: make it possible to configure the state store as an in-memory store in FFI 2023-07-10 12:04:52 +02:00
Richard van der Hoff 708a7d95a7 Add Qr.state() 2023-07-10 11:45:33 +02:00
Richard van der Hoff 0ccb1efded Enable rust-sdk tracing for device tests
... because knowing why your tests are failing is nice
2023-07-10 11:45:33 +02:00
Richard van der Hoff 0a5ef29c34 device.test: Stop pretending we have independent tests
This file claimed to have lots of tests, but actually they are not independent
- they are just one big test.

It's not ideal to have one massive test like this, but I don't really have time
to rewrite them and we should stop pretending.
2023-07-10 11:45:33 +02:00
Damir Jelić 9f68b3838f Merge pull request #2142 from matrix-org/andybalaam/cache-last-event-in-roominfo
Store the last timeline event in RoomInfo
2023-07-10 11:30:13 +02:00
Jonas Platte 0a6234f40a ffi: Send initial back-pagination status to subscriber immediately
… right as the subscriber is registered.
2023-07-10 11:07:43 +02:00
Kévin Commaille 4e52125551 ui: Allow to filter events added to the Timeline
To avoid to deal with events we are not interested in in the stream.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
2023-07-07 22:20:14 +02:00
Richard van der Hoff 0bf99d5c73 Fix handling of SAS start events once we have shown a QR code (#2242)
Fixes #2237

Also a couple of other cleanups while I'm in the area.
2023-07-07 17:55:13 +01:00
Andy Balaam bac555d31b More CI fixes 2023-07-07 16:19:38 +01:00
Andy Balaam ff0cd7c592 Fix one more JS test 2023-07-07 15:55:29 +01:00
Andy Balaam 96098e8f8b More feature flag fiddling 2023-07-07 15:44:16 +01:00
Andy Balaam 6e84f969ad More feature flag fiddling 2023-07-07 15:42:52 +01:00
Andy Balaam 9bcc1e9e00 More JavaScript test updates 2023-07-07 15:31:09 +01:00
Andy Balaam 31e9572d42 Allow unused code for read_only 2023-07-07 15:22:45 +01:00
Andy Balaam b525491bd5 Fix warnings from CI 2023-07-07 15:20:41 +01:00
Andy Balaam c9a11a58bf Format JavaScript 2023-07-07 15:11:00 +01:00
Andy Balaam 82c7a05f01 Fixes for problems found through CI 2023-07-07 15:05:48 +01:00
Nicolas Mauri fa4c1ef00b ffi: expose the notification settings (#2223) 2023-07-07 15:07:31 +02:00
Andy Balaam a2ca168573 Provide path for a doc link 2023-07-07 13:22:28 +01:00
Andy Balaam 6cb3afe6fd Merge branch 'main' into andybalaam/cache-last-event-in-roominfo 2023-07-07 13:14:52 +01:00
Andy Balaam 3fd1542a25 Fetch sender profile when returning latest event 2023-07-07 12:59:49 +01:00
Benjamin Bouvier 1f47e165ab chore: remove a few useless async 2023-07-07 13:04:31 +02:00
Benjamin Bouvier 4e065ccee9 feat: don't recreate the cross-process lock if it already existed 2023-07-07 13:04:16 +02:00
Jonas Platte 0ef819bca7 ffi: Use proc-macros for SessionVerificationControllerDelegate 2023-07-06 18:26:40 +02:00
Jonas Platte 6f295e2571 Upgrade UniFFI 2023-07-06 18:26:40 +02:00
Jonas Platte 07a82c841f ui: Spawn a background task for decryption retrying 2023-07-06 17:55:05 +02:00
Andy Balaam 98882b9c23 Store the last timeline event in RoomInfo
and the latest few encrypted events in Room.
Use the latest event to provide a room preview, and use the encrypted
events to replace the laest event when they are decrypted.
2023-07-06 16:48:27 +01:00
Florian Duros c600c64b95 BindingsJS: Release v0.1.1 (#2234) 2023-07-06 17:25:02 +02:00
Ivan Enderlin 61ddf047e0 fix(sqlite): Prevent reaching the maximum number of parameters in an SQL statement
fix(sqlite): Prevent reaching the maximum number of parameters in an SQL statement
2023-07-06 17:07:23 +02:00
Ivan Enderlin a70e79420c fix(sqlite): Ensure there is free space for static parameters, in chunk_large_query_over. 2023-07-06 16:46:35 +02:00
Ivan Enderlin 4e20dc5047 chore(sqlite): To make a friend smile. 2023-07-06 16:27:32 +02:00
Ivan Enderlin 23dae0612d chore(sqlite): Use itertools in repeat_vars. 2023-07-06 16:22:37 +02:00
Jonas Platte fe189fda06 bindings: Rename subscribe_to_back_pagination_{status => state}
For consistency with the UI crate.
2023-07-06 15:55:49 +02:00
Florian Duros 0384a52f1e Expose the verify method for the Device in the bindings (#2229) 2023-07-06 15:46:52 +02:00
Ivan Enderlin d33e2aff2e doc(sqlite): Fix a typo. 2023-07-06 15:18:41 +02:00
Ivan Enderlin 380bce0604 fix(sqlite): Prevent reaching the maximum number of parameters in an SQL statement.
This patch introduces a new `chunk_large_query_over` function. Imagine
there is a _dynamic_ query that runs potentially large number of
parameters, so much that the maximum of parameters can be hit. Then,
this function is for you. It will execute the query on chunks of
parameters.

`chunk_large_query_over` uses `Vec::split_off` to avoid cloning `Key`s
as much as possible. It's difficult to use references here because of
the async nature of `SqliteObjectExt::prepare`.

This patch updates `get_kv_blobs`, `get_room_infos`,
`get_maybe_stripped_state_events_for_keys`, `get_profiles`,
`get_user_ids`, and `get_display_names` to use this new function.

This patch finally adds the `repeat_vars` function to replace in a
more efficient way the `vec!["?"; n].join(", ")` pattern. There is less
memory allocations.
2023-07-06 15:11:42 +02:00
Jonas Platte ae17273a37 ui: Replace loading indicator and timeline start virtual timeline items
… with a BackPaginationState observable.
2023-07-06 13:57:40 +02:00
Damir Jelić 75dbfb4d46 Remove an unnecessary unwrap in the crypto crate (#2226) 2023-07-06 12:40:04 +02:00
Ivan Enderlin f2d53a272b Merge pull request #2222 from Hywan/chore-ffi-sliding-sync-bye-bye 2023-07-06 10:11:30 +02:00
Jonas Platte 438c0076f7 ui: Remove NewEventTimelineItem 2023-07-05 17:47:06 +02:00
Jonas Platte b9f98846c8 ci: Upgrade typos 2023-07-05 17:47:06 +02:00
Ivan Enderlin 6cca4ab3e5 chore(ffi): Bye bye Sliding Sync bindings!
The `RoomList` and `EncryptionSync` API provide a better developer
experience and address concrete needs. Nobody no longer uses the Sliding
Sync bindings, so we can remove them.
2023-07-05 15:05:08 +02:00
Ivan Enderlin 96ef45afe8 feat: Immediately fail if a sliding sync request failed (follow up of #2209)
feat: Immediately fail if a sliding sync request failed (follow up of #2209)
2023-07-05 14:47:54 +02:00
Ivan Enderlin 9493c8a75d chore(sdk): Feedbacks. 2023-07-05 14:29:16 +02:00
Ivan Enderlin 1ccc0c3df3 feat(sdk): Ensure that the task sending E2EE requests cannot be detached. 2023-07-05 13:35:52 +02:00
Benjamin Bouvier 1cd87ca15d feat: Immediately fail if a sliding sync request failed
For sliding sync that starts both the actual sliding sync request along the e2ee requests,
we need to make sure that, in case of failure of sliding sync, we reset `pos` as soon as possible.
With the code before this patch, the sliding sync response might be available, but the whole
processing could be waiting for the e2ee requests to finish. In the updated version, we spawn
the e2ee requests in a background task, then run the sliding sync request immediately and fail
if it failed.

Another nice benefit is that the e2ee requests won't be interrupted in the middle of processing,
if the sliding sync changed parameters and we cancelled the whole sync a bit too early.

Fixes #2206.
2023-07-05 13:13:51 +02:00
Jonas Platte afeb5dab24 sdk: Remove questionable use of assign! macro 2023-07-05 12:16:30 +02:00
Jonas Platte 206accb5fb Bump serde_html_form 2023-07-05 11:34:50 +02:00
Jonas Platte 0b24df991e Bump bs58 2023-07-05 11:34:50 +02:00
Jonas Platte dd2fefa2f3 Bump indexmap 2023-07-05 11:34:50 +02:00
Jonas Platte 2b72df7c06 Bump itertools 2023-07-05 11:34:50 +02:00
Jonas Platte 71f7f7c69e Remove mentions of labs crates
There are none at the moment.
2023-07-05 11:34:50 +02:00
Jonas Platte 4ae63caa56 Bump bitflags dependency
2.3.3 contains some bug fixes, might be relevant.
2023-07-05 11:34:50 +02:00
Ivan Enderlin b6908c0611 feat(ui): Change all_rooms batch size to 200
feat(ui): Change `all_rooms` batch size to 200
2023-07-05 10:46:01 +02:00
Ivan Enderlin 36419a7a1f chore: get rid of previous locking crypto-store methods
chore: get rid of previous locking crypto-store methods
2023-07-05 10:35:33 +02:00
Ivan Enderlin 12a1f25ec3 feat(ui): Change all_rooms batch size to 200.
`RoomListServer` defines an `all_room` sliding sync list. This list
starts in selective sync-mode, then it switches to growing sync-mode.
The previous batch size of the growing sync-mode was 50. This patch
updates it to 200, because empirically it seems a better value for
perceived performance.

This patch also rewrites how `State::next` is written. No change in the
code, just comestic.
2023-07-05 10:28:02 +02:00
Ivan Enderlin a0f40606d4 Merge pull request #2208 from bnjbvr/only-restart-growing-on-errors
feat: Only restart growing the allRooms list in case of errors
2023-07-05 10:01:58 +02:00
Jonas Platte da43d81095 ui: Keep loading indicator at the top when processing timeline events 2023-07-05 09:52:59 +02:00
Jonas Platte 5fa293b8ed crypto: Use HashMap inside DashMap
… instead of nesting DashMaps.
The previous type was tracking mutability at very fine-grained level for
no clear reason.
2023-07-04 19:50:45 +02:00
Jonas Platte 87782e9567 ffi: Expose EventTimelineItem::origin 2023-07-04 19:50:08 +02:00
Jonas Platte f344aa7669 sdk: Add EventTimelineItem::origin 2023-07-04 19:50:08 +02:00
Mauro Romito f4929fd064 joined members is now in the notification 2023-07-04 19:39:43 +02:00
Jonas Platte cb2e97f06a Upgrade eyeball to 0.8 2023-07-04 16:22:45 +00:00
Nicolas Mauri 3db72101f3 sdk: Allow to update notification settings (#2135)
* sdk: Allow to update notification settings

* Add an event listener for push rule events

* Fix: simplify insertion and deletion of rules

* Fix: Limit rules cloning

* Fix: Unit tests

* Fix: set_room_notification_mode

* Fix: potential race condition when updating the local ruleset

* Refactor RuleCommands

* RuleCommands Unit Tests

* Fix: limit lock usage

* nit: use expression assignment by default

* nit: pass Ruleset by ownership in RuleCommands' ctor

* a few nits: use to_owned() for &str -> String; use free function for notify actions; use clone() where it's more obvious

and use explicit variants so we know we have to consider this match if we were to add a new variant later

* nit: rename RuleCommands::set_enabled to set_enabled_internal

* nit: test modules don't need to be publicized

* tidy tests

* nit: pass an owned RuleCommands in Rules::apply

* add comments/questions in Rules tests

* nit: no need to publicize the tests module

* tweak NotificationSettings tests too

* rustfmt

---------

Co-authored-by: Benjamin Bouvier <public@benj.me>
2023-07-04 15:29:57 +00:00
Benjamin Bouvier bb34f69662 chore: get rid of previous locking crypto-store methods 2023-07-04 17:19:01 +02:00
Benjamin Bouvier e8a306132a feat: Only restart growing the allRooms list in case of errors
When the sync has been terminated and restarts (e.g. an app is going from the background to the foreground), then
the sliding sync is always restarting in growing mode, independently of the previous state of the sync (error or normal
termination). This is something the current sliding sync proxy can't handle nicely, because it prioritizes a change in
parameters over live data; as a matter of fact, to alleviate the burden of the proxy, we can try to only restart in
growing mode if we ran into an error (and not if we had a normal termination).
2023-07-04 13:06:49 +02:00
Jonas Platte 7bcc886429 Re-format with latest rustfmt 2023-07-03 18:57:05 +02:00
Jonas Platte 37023fe90b Fix new clippy lint 2023-07-03 18:57:05 +02:00
Jonas Platte cd4288391e Bump nightly toolchain version 2023-07-03 18:57:05 +02:00
Jonas Platte b681f234a3 Upgrade criterion, pprof 2023-07-03 18:08:01 +02:00
Alfonso Grillo 147cd970c4 Map MSC3488 fields for UniFFI (#2187)
* Update ruma

* Refactor send_location

* Add timeline parser

* Fix code format

* Add zoom_level

* Update ruma

* Map zoom_level

* Format code

* Cleanup

* Refactor LocationContent

* Apply suggestion

* Fix format issue
2023-07-03 13:34:07 +00:00
Ivan Enderlin 10c9a47f01 Merge pull request #2197 from Hywan/test-ui-room-list-assert-equal 2023-07-03 13:36:55 +02:00
Ivan Enderlin 746b13071e Merge pull request #2198 from Hywan/test-integration-fix 2023-07-03 13:36:06 +02:00
Ivan Enderlin c955de8331 test: Change the installer for setup-matrix-synapse.
There is a bug in Synapse and changing the installer seems to fix the
problem.
2023-07-03 12:47:24 +02:00
Ivan Enderlin 0c13b76d27 test(ui): Do strict assert comparison in some tests of RoomList.
It ensures that we know exactly what's happening here. Tests are still
valid without any changes in the expected data, but it prevents a
potential breaking.
2023-07-03 11:13:45 +02:00
Ivan Enderlin 4f84e33457 fix(ui): Add bump_event_types onto visible_rooms
fix(ui): Add `bump_event_types` onto `visible_rooms`
2023-07-03 10:04:31 +02:00
Ivan Enderlin 37ad562b4b test(sdk): Use async_test instead of tokio::test. 2023-07-03 09:36:45 +02:00
Ivan Enderlin ed2d3bc5b3 chore(ui) Use a function to configure all_rooms and visible_rooms.
The Sliding Sync list `all_rooms` and `visible_rooms` of the `RoomList`
must have the exact same configurations for `sort`, `filters`, and
`bump_event_types`. Instead of maintaining the same code, this patch
adds a new function: `configure_all_or_visible_rooms_list` that
configures the lists exactly the same. This function also explicitely
configures `sort` so that we do no rely on the default values from
`SlidingSyncListBuilder`.
2023-07-03 09:22:32 +02:00
Ivan Enderlin 627a1613df fix(ui): Add bump_event_types onto visible_rooms.
This patch configures the same `bump_event_types` for the
`visible_rooms` list than for the `all_rooms` list, so that we may be
sure that the sorting/ordering is the same between the two lists.
2023-06-30 21:48:30 +02:00
Ivan Enderlin f82fd896f9 chore(ui): Rename room_list module to room_list_service
chore(ui): Rename `room_list` module to `room_list_service`
2023-06-30 21:45:10 +02:00
Ivan Enderlin 9f90b6d4a8 chore(ui): Rename room_list module to room_list_service. 2023-06-30 19:59:13 +02:00
jonnyandrew 3180a1ba7c ffi: Expose proxy and insecure SSL settings (#2191)
Co-authored-by: Damir Jelić <poljar@termina.org.uk>
2023-06-30 15:31:20 +02:00
Benjamin Bouvier 4fbb3d2b63 xtask: add --features testing to most testing tasks 2023-06-30 12:41:04 +02:00
Benjamin Bouvier 66e1b94d31 test: add test for the generational counter invalidation 2023-06-30 12:41:04 +02:00
Benjamin Bouvier d642c22db7 feat: implement a generation counter for the CryptoStore lock 2023-06-30 12:41:04 +02:00
Benjamin Bouvier d72fd34325 Make pending local echoes sticky, take 2 (#2189)
* ui: Move EventSendState into timeline::event_item::local module

* [WIP] ui: Make pending local echoes stick to the bottom of the timeline

* test: update more test expectations

* chore: tweak comment to slightly better reflect reality

* nit: remove else after return

* fix: the item's insert position is insert_idx, not `items.len()` anymore

* fix: look for remote echo before local echo when processing send state

Previous code assumed that the latest timeline items would be the most recent, and that
if there was a remote echo, it would always be after the local echo, because of that.
That's not the case anymore, so we must look for possibly a remote echo first, and then
if we find it, apply the late update process.

Also, there might remain a day divider added by the local echo, if it were inserted last.
I'm not sure it covers all the cases, but I've now made it so that the day divider is removed
if it was the last element.

* feat: switch strategy; keep on pushing if there's nothing in the timeline yet

* Revert "test: update more test expectations"

This reverts commit 400cc93ba7c98042a28b5e8d5042899e854f6cff.

* test: reset test expectations

* Address review comments

* fix: don't mix up latest event with any status, with latest non-failed event index

---------

Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-06-30 09:37:49 +00:00
Richard van der Hoff 5348e7cc12 Airplane -> Aeroplane
For verification-by-emoji, the spec uses the word "Aeroplane" rather than
"Airplane". Element-web expects us to use the specced words (and will otherwise
complain about the lack of i18n data).

It seems like following the spec will help maintain consistency.
2023-06-30 10:45:52 +02:00
jonnyandrew f508109d0c Fix reaction response resolution 2023-06-30 10:26:41 +02:00
Benjamin Bouvier 77290ff2c1 feat: Get rid of "common extensions"
Common extensions are confusing, and they've included `e2ee` and `to-device` by default. This is not a sane default anymore,
now that there's the concept of `EncryptionSync`: it's either we have the encryption sync that enables e2ee and to-device +
a room list sync that doesn't, OR we have a single room list that has both.

Room List was misconfigured to always use `e2ee` and `to-device`, which was incorrect when it's running with the `EncryptionSync`
in the background. This is now removed, and properly tested.
2023-06-30 09:56:29 +02:00
Richard van der Hoff 48a6bffc1e Merge branch 'release-matrix-sdk-crypto-js-0.1.0' 2023-06-29 17:02:40 +01:00
Ivan Enderlin 1958df22a9 fix(sdk): Change how SlidingSyncList::update_room_list handles updates for out of list rooms
fix(sdk): Change how `SlidingSyncList::update_room_list` handles updates for out of list rooms
2023-06-29 15:48:39 +02:00
Ivan Enderlin 91bf84714d feat(ffi): Add methods on RoomListItem so that full_room isn't necessary
feat(ffi): Add methods on `RoomListItem` so that `full_room` isn't necessary
2023-06-29 15:39:45 +02:00
Richard van der Hoff 757716c924 matrix-sdk-crypto-js v0.1.0 2023-06-29 15:22:00 +02:00
Richard van der Hoff 1b2b6c011b update changelog 2023-06-29 15:22:00 +02:00
Ivan Enderlin bd148fd422 chore(ffi): RoomListItem::is_direct returns a bool. 2023-06-29 15:14:53 +02:00
Ivan Enderlin 363b0d9ee6 doc(ffi): Add some precisions. 2023-06-29 15:07:46 +02:00
Benjamin Bouvier 73ad51879a feat: implement time lease based locks for the CryptoStore (#2140)
This implements a new time lease based lock for the `CryptoStore`, that doesn't require explicit unlocking, so that's more robust in the context of #1928, where any process may die because the device is running out of battery, or unexpected flows cause a lock to not be released properly in one or the other process.

```
//! This is a per-process lock that may be used only for very specific use
//! cases, where multiple processes might concurrently write to the same
//! database at the same time; this would invalidate crypto store caches, so
//! that should be done mindfully. Such a lock can be acquired multiple times by
//! the same process, and it remains active as long as there's at least one user
//! in a given process.
//!
//! The lock is implemented using time-based leases to values inserted in a
//! crypto store. The store maintains the lock identifier (key), who's the
//! current holder (value), and an expiration timestamp on the side; see also
//! `CryptoStore::try_take_leased_lock` for more details.
//!
//! The lock is initially acquired for a certain period of time (namely, the
//! duration of a lease, aka `LEASE_DURATION_MS`), and then a "heartbeat" task
//! renews the lease to extend its duration, every so often (namely, every
//! `EXTEND_LEASE_EVERY_MS`). Since the tokio scheduler might be busy, the
//! extension request should happen way more frequently than the duration of a
//! lease, in case a deadline is missed. The current values have been chosen to
//! reflect that, with a ratio of 1:10 as of 2023-06-23.
//!
//! Releasing the lock happens naturally, by not renewing a lease. It happens
//! automatically after the duration of the last lease, at most.
```

---

* feat: implement a time lease based lock for the crypto store
* feat: switch the crypto-store lock a time-leased based one
* chore: fix CI, don't use unixepoch in sqlite and do time math in rust
* chore: dummy implementation in indexeddb, don't run lease locks tests there
* feat: in NSE, wait the duration of a lease if first attempt to unlock failed
* feat: immediately release the lock when there are no more holders
* chore: clippy
* chore: add comment about atomic sanity
* chore: increase sleeps in timeline queue tests?
* feat: lower lease and renew durations
* feat: keep track of the extend-lease task
* fix: increment num_holders when acquiring the lock for the first time
* chore: reduce indent + abort prev renew task on non-wasm + add logs
2023-06-29 13:05:44 +00:00
Ivan Enderlin 0cd0ade2bc feat(ffi): Add methods on RoomListItem so that full_room isn't necessary.
Calling `RoomListItem::full_room` creates its associated `Timeline`.

ElementX calls `full_room` to get the `is_direct`, `avatar_url` and
`canonical_alias` values for _all_ rooms in the room list. Instead of
doing so, let's add those methods directly in `RoomListItem` so that the
`Timeline` isn't created for _all_ rooms.
2023-06-29 14:48:37 +02:00
Ivan Enderlin eab1f8783a chore(ui): Remove unecessary qualification
chore(ui): Remove unecessary qualification
2023-06-29 14:18:09 +02:00
Richard van der Hoff 9a2f6b8fb9 matrix-sdk-crypto-js v0.1.0
Release `crypto-js` / Publish 🕸 [m]-crypto-js (push) Failing after 1m35s
2023-06-29 13:10:16 +01:00
Richard van der Hoff 082d166502 update changelog 2023-06-29 13:09:51 +01:00
Ivan Enderlin 717063eaa8 feat(common): Implement RingBuffer::drain
feat(common): Implement `RingBuffer::drain`
2023-06-29 13:49:37 +02:00
Ivan Enderlin 9b4d06272c chore(ui): Remove unecessary qualification. 2023-06-29 13:37:45 +02:00
Ivan Enderlin 6d67375f8e fix(ffi): Simplify Room::add_timeline_listener
fix(ffi): Simplify `Room::add_timeline_listener`
2023-06-29 13:24:05 +02:00
Ivan Enderlin e96097a085 feat(common): Implement RingBuffer::drain.
This patch implements `RingBuffefr::drain` because it's going to be
useful soon.
2023-06-29 13:22:43 +02:00
Richard van der Hoff 506d7c6f9b Add accessor for remaining time to VerificationRequest. (#2178) 2023-06-29 11:49:31 +01:00
Benjamin Bouvier 459b689953 tests: add unit tests for the CryptoStoreLock 2023-06-29 12:34:53 +02:00
Ivan Enderlin 61b7335dce chore(sdk): Log SlidingSync list updates
chore(sdk): Log SlidingSync list updates
2023-06-29 11:34:04 +02:00
Ivan Enderlin 62315d726f feat(ui): Do not update the viewport of the room list if it hasn't changed
feat(ui): Do not update the viewport of the room list if it hasn't changed
2023-06-29 11:33:45 +02:00
Ivan Enderlin 9bae039629 fix(sdk): Change how SlidingSyncList::update_room_list handles updates for out of list rooms.
So. `SlidingSyncList::update_room_list()` does the following:

1. It adjust room list entries,
2. It updates the `maximum_number_of_rooms`,
3. It applies the sync operations on the `room_list` entries,
4. It updates the `room_list` entries for rooms outside the sync
   operations.

SlidingSync answers with a `lists` and `rooms`. The `room_list` entries
is updated as follows: either a sync operation from `lists` has been
applied and the entries are updated, or `rooms` contains rooms that
are not updated by `lists` but that receive new events, and thus must
trigger an update in the `room_list` entries.

This patch changes a little bit the last part of this. Initially,
we were updating rooms that are part of `rooms` but absent of
`lists` sync operations, only **for the current list's ranges**.
But that's wrong! First, we have noticed a bug here: the
correct code wasn't `skip(start).take(end.saturating_add(1))` but
`skip(start).take(end.saturating_add(1) - start)`. Note a big deal, we
were iterating on more entries like it was necessary, but everything
was filtered later, so no bug, just useless computations. Second,
this `skip` and `take` is actually… useless. A room to which we have
subscribed can be out of any range, but we still want `room_list`
entries to receive an update for that particular room.

This patch fixes that once and for all.

In practise, the bug wasn't happening because if someone subscribes
to a room, its timeline is likely to be fetched, and updates will be
received, but still, this is better now from a SlidingSync strict point
of view.
2023-06-29 11:21:31 +02:00
Ivan Enderlin d01725dedb fix(ffi): Simplify Room::add_timeline_listener.
This patch does the following:

1. changes `add_timeline_listener` to be async, thus removing one
  `RUNTIME.block_on`.
2. simplifies the logic by adopting a more classical pattern we use
   elsewhere:
  * removes one `spawn_blocking`,
  * let's move the `listener` into the task as a `Box` instead of
    cloning it as an `Arc`.
2023-06-29 10:31:04 +02:00
Ivan Enderlin 111071c034 chore(sdk): Log SlidingSync list updates.
This patch removes an old log and adds a better one with more context.
2023-06-29 09:47:39 +02:00
Ivan Enderlin af4ebd8422 feat(ffi): Update RoomListService::apply_input to always return (). 2023-06-29 09:21:14 +02:00
Ivan Enderlin de09b781ec feat(ui): Do not update the viewport of the room list if it hasn't changed.
Imagine the room list has the viewport set to the range `0..=19`. The
user scrolls quickly to `50..=69` to see something below and immediately
scrolls back to its initial position, without stopping the scroll
at any moment in between. The app using this API might update the
viewport from `0..=19` to… `0..=19`. Updating the viewport, updates the
`visible_rooms` sliding sync list. Since a list is modified, the current
sync-loop iteration is skipped over and a new one restarts, i.e. the
current in-flight request is cancelled… for nothing.

This patch prevents this situation. The current viewport ranges is
stored in `RoomListService`. `RoomListService::apply_input` used to
return `Result<(), Error>`, now it returns `Result<InputResult, Error>`.
The `InputResult` enum is a new type. It represents whether an input has
been applied or ignored, which must be differentiate from errors.

So now, if the viewport changes and it's not different from the previous
value, `InputResult::Ignored` is returned.
2023-06-29 09:21:14 +02:00
Richard van der Hoff 5d9cc6be15 use assert_matches instead of manual panics 2023-06-28 16:32:07 +02:00
Richard van der Hoff c0c7c598b5 When refusing a verification, send cancel to the originating device
According to the spec, when a device receives a verification request and wants
to refuse it, it should send the `m.key.verification.cancel` to the originating
device, rather than broadcasting it.
2023-06-28 16:32:07 +02:00
Andy Balaam 33243cb9fb Fix comment wrongly referring to vector.
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2023-06-28 14:07:10 +02:00
Andy Balaam 785eccc004 Add capacity to RingBuffer 2023-06-28 14:07:10 +02:00
Andy Balaam 13c7f5b0c6 Implement Default for RingBuffer 2023-06-28 14:07:10 +02:00
Richard van der Hoff 3f2eb5828c crypto-js: wait for device updates in getIdentity
We previously did something similar for `getUserDevices` in 0da8e56a. Turns out
it's useful for `getIdentity` too.
2023-06-28 13:33:23 +02:00
Jonas Platte e732b3630a ffi: Add SendAttachmentJoinHandle 2023-06-28 12:55:56 +02:00
Jonas Platte b088339710 Revert "crypto: Add Store::room_keys_for_room_received_stream"
This reverts commit e87b3def02.
2023-06-28 12:32:18 +02:00
Jonas Platte 90e39f7be2 Revert "sdk: Re-export room_keys[_for_room]_received_stream from Encryption"
This reverts commit 04d56130c3.
2023-06-28 12:32:18 +02:00
Jonas Platte fd5b28bb51 Revert "sdk: Add room::Joined::room_keys_received_stream"
This reverts commit 9f72e2cf16.
2023-06-28 12:32:18 +02:00
Jonas Platte b53eba5ec9 Revert "ui: Use new room_key stream for retrying decryption in Timeline"
This reverts commit 06600ac53f.
2023-06-28 12:32:18 +02:00
Ivan Enderlin 07b7885915 chore(sdk): Add missingn newline. 2023-06-28 11:10:53 +02:00
Jonas Platte 06600ac53f ui: Use new room_key stream for retrying decryption in Timeline 2023-06-28 10:14:59 +02:00
Jonas Platte 9f72e2cf16 sdk: Add room::Joined::room_keys_received_stream 2023-06-28 10:14:59 +02:00
Jonas Platte 04d56130c3 sdk: Re-export room_keys[_for_room]_received_stream from Encryption 2023-06-28 10:14:59 +02:00
Jonas Platte e87b3def02 crypto: Add Store::room_keys_for_room_received_stream 2023-06-28 10:14:59 +02:00
Ivan Enderlin 07700abf75 feat: Cache the RoomListService's rooms
feat: Cache the `RoomListService`'s rooms
2023-06-28 08:58:45 +02:00
Ivan Enderlin 9ab4fa0a8b chore(sdk): Log the SyncOps
chore(sdk): Log the `SyncOp`s
2023-06-28 08:41:00 +02:00
Andy Balaam 86fe3364ed Add serialization, clear and extend to RingBuffer 2023-06-27 17:25:25 +00:00
Jonas Platte 81ca1c5072 ui: Add a test for thread message reply fallback 2023-06-27 19:23:14 +02:00
Jonas Platte 4ec8bec2fc ui: Treat thread fallback the same as a regular reply
… for now, until we add proper support for threads.
2023-06-27 19:23:14 +02:00
Benjamin Bouvier 98fed1d2eb chore: use smaller critical sections
This opens up a possible race condition where an embedder calls the method twice, generating two new rooms,
but then it's fine as long as they don't cache them somewhere, which we expect they don't.
2023-06-27 16:29:35 +02:00
Benjamin Bouvier e3df97421b chore: remove useless async 2023-06-27 16:27:31 +02:00
Jonas Platte b08480baea ui: Move message-sending loop start log event
… such that it gets the timeline builder's tracing span.
2023-06-27 16:18:04 +02:00
Benjamin Bouvier ccd5e877eb feat: Cache the RoomListService's rooms
That avoids recreating a timeline object every time a user calls `RoomListService::room()` with the same room id, so that should speed up
operations like fetching the latest event for rooms we've already entered before.
2023-06-27 16:05:26 +02:00
Damir Jelić fed583c5c6 Emit room keys after they have been persisted, not before. 2023-06-27 14:30:08 +02:00
Doug d4252c4e4a feat(bindings): Add user_agent parameter to authentication_service. 2023-06-27 12:59:22 +02:00
Ivan Enderlin 61202ec19a feat(common): Propose a simple RingBuffer implementation
feat(common): Propose a simple `RingBuffer` implementation
2023-06-26 19:52:57 +02:00
Ivan Enderlin ad1b9056ae feat(common): Add RingBuffer::is_empty. 2023-06-26 19:30:27 +02:00
aringenbach 47030e7883 ffi: Use toggle_reaction and update API 2023-06-26 16:08:50 +00:00
Ivan Enderlin d69be5ee75 feat(common): Propose a simple RingBuffer implementation.
This patch propose a very simple `RingBuffer` implementation based on
`std::collections::VecDeque`.
2023-06-26 17:49:41 +02:00
jonnyandrew 7e7996d43d ui: Add local echoes for reactions to Timeline 2023-06-26 15:17:48 +00:00
Ivan Enderlin 04580f75a8 chore(sdk): Log the SyncOps. 2023-06-26 16:04:11 +02:00
Jonas Platte 49b1e8732c Upgrade most of our dependencies
Still holding back js-sys and web-sys due to unresolved wasm-bindgen issues.
2023-06-26 14:32:24 +02:00
Ivan Enderlin ba422f861e fix(sdk): Change SlidingSyncList::room_list's capacity
fix(sdk): Change `SlidingSyncList::room_list`'s capacity
2023-06-26 14:16:05 +02:00
Ivan Enderlin 749911af60 fix(sdk): Change SlidingSyncList::room_list's capacity.
This patch changes the capacity of the internal buffer of
`ObservableVector` for `SlidingSyncList::room_list` from 16 to 4096.
With an increased capacity, we reduce the probability to send a
`VectorDiff::Reset` to subscribers. `Reset` are happening quite often
for apps using `matrix-sdk`, and it impacts their performance. This
patch tries to improve this situation. This `room-list` contains
`RoomListEntry`, which is can cheap in terms of memory, compared to the
impact of sending `Reset`s too often. That's a tradeoff.
2023-06-26 13:52:15 +02:00
Jonas Platte 0e1f74f617 Increase other sleep in test a bit 2023-06-26 11:36:24 +02:00
Jonas Platte b6353f82a8 ui: Improve logging for message-sending queue 2023-06-26 11:36:24 +02:00
Benjamin Bouvier 171c1cf25b chore: fix documentation
Signed-off-by: Benjamin Bouvier <public@benj.me>
2023-06-26 11:22:38 +02:00
Benjamin Bouvier b442d30848 feat: remove the in-memory cache for to_device_token in sliding sync
Signed-off-by: Benjamin Bouvier <public@benj.me>
2023-06-26 11:22:38 +02:00
Benjamin Bouvier 94fa888b7b feat: always enable the SS cache by default
This makes sure that the to-device prev_batch/since token is already saved to and reloaded from disk.

Signed-off-by: Benjamin Bouvier <public@benj.me>
2023-06-26 11:22:38 +02:00
Benjamin Bouvier 69932adb7e chore: remove unreachable UI notifications module
Remnant from a bad merge/renaming.

Signed-off-by: Benjamin Bouvier <public@benj.me>
2023-06-26 11:22:38 +02:00
Mauro 2c58c65add ffi: Allow disabling local notification filtering using push rules 2023-06-26 08:49:06 +00:00
Ivan Enderlin 8916eb6409 test(ui): Replace sleep by yield_now
test(ui): Replace `sleep` by `yield_now`
2023-06-23 16:37:00 +02:00
Jonas Platte 44ef302bc7 Increase sleep in test a bit
since it seems flaky in CI
2023-06-23 16:17:21 +02:00
Ivan Enderlin 40db35abe7 test(ui): Replace sleep by yield_now.
Eh, removing `sleep` from the tests is always a pleasure right?
2023-06-23 16:15:57 +02:00
Jonas Platte 9b802998b3 ui: Put retries in the message-sending queue 2023-06-23 14:03:27 +02:00
Jonas Platte 0a37ce50a8 ui: Allow retrying cancelled local echoes 2023-06-23 14:03:27 +02:00
Alfonso Grillo e72bae1a8f ffi: Add send_location api 2023-06-23 11:29:01 +00:00
Jonas Platte 4c24007cb8 ui: Add a queue to serialize message sending requests 2023-06-23 09:41:07 +02:00
Jonas Platte cb6d3c3c47 ui: Add a failing test for message ordering 2023-06-23 09:41:07 +02:00
David Langley 19ba18d788 ffi: Expose the senders of reactions 2023-06-23 08:56:16 +02:00
Ivan Enderlin 73de71bd42 feat(ui): Welcome to RoomListService
feat(ui): Welcome to `RoomListService`
2023-06-22 17:36:05 +02:00
Ivan Enderlin 1c6ad4a082 chore(ui): Address feedbacks. 2023-06-22 17:16:25 +02:00
Ivan Enderlin 9eadbc302f feat(ffi): Rename Client::room_list to Client::room_list_service. 2023-06-22 17:08:30 +02:00
Ivan Enderlin 93d619249c test(ui): Reduce latency of the tests. 2023-06-22 15:37:25 +02:00
Ivan Enderlin 35ee6db152 fix(ffi): Eh UniFFI, we want Tokio compat. 2023-06-22 14:59:51 +02:00
jonnyandrew 7fd5068faa ui: Deduplicate reaction senders in timeline 2023-06-22 12:42:49 +00:00
Ivan Enderlin 1631b5ba18 chore(ui): Use matrix_sdk::executor instead of tokio. 2023-06-22 14:15:10 +02:00
Ivan Enderlin 083a79a408 doc(ui): Fix intra-links. 2023-06-22 14:08:09 +02:00
Ivan Enderlin f9fe7450ce Merge branch 'main' into feat-ui-roomlist-service 2023-06-22 13:48:00 +02:00
Ivan Enderlin d43d5b515e chore: Make Clippy happy. 2023-06-22 13:39:00 +02:00
Ivan Enderlin eb2213467a feat(ffi): Implement RoomList::loading_state. 2023-06-22 12:12:45 +02:00
Ivan Enderlin 398f70c9de feat(ui): Implement RoomListLoadingState.
The type is well documented. Go check it out :-).
2023-06-22 11:43:38 +02:00
Ivan Enderlin c9e3d7988f feat(ui): Change how room_list::State is updated.
Instead of broadcasting an intermediate update for the `State`, it's
updated once after the sync.
2023-06-22 10:23:45 +02:00
Ivan Enderlin d77a48cc2c feat(ffi): Implement RoomList::room. 2023-06-21 17:20:05 +02:00
Ivan Enderlin 40602e7bba chore(ffi): Update according to previous commits. 2023-06-21 17:11:26 +02:00
Ivan Enderlin b774110235 chore(ui): Move methods from RoomListService inside RoomList.
This patch moves `RoomListService::entries` and `::filtered_entries`
inside `RoomList`. It also implements `RoomList::new`. Finally,
it implements `RoomListService::all_rooms` and re-implement
`RoomListService::invites`.

Basically:

```rust
// 1.
room_list.entries().await?
// 2.
room_list.invites().await?
```

becomes:

```rust
// 1.
room_list.all_rooms().await?.entries()
// 2.
room_list.invites().await?.entries()
```

`all_rooms` and `invites` both return a `RoomList`.
2023-06-21 17:00:30 +02:00
Ivan Enderlin b01bbe7e92 chore: Add missing copyright headers. 2023-06-21 16:29:55 +02:00
Ivan Enderlin dc061308bd chore(ui): Rename RoomList to RoomListService. 2023-06-21 16:29:03 +02:00
535 changed files with 75832 additions and 40250 deletions
+15 -2
View File
@@ -1,7 +1,7 @@
# Pass the rustflags specified to host dependencies (build scripts, proc-macros)
# when a `--target` is passed to Cargo. Historically this was not the case, and
# because of that, cross-compilation would not set the rustflags configured
# below in `target.'cfg(all())'` for them, resulting in cache invalidation.
# below in `target.'cfg(...)'` for them, resulting in cache invalidation.
#
# Since this is an unstable feature (enabled at the bottom of the file), this
# setting is unfortunately ignored on stable toolchains, but it's still better
@@ -15,7 +15,12 @@ uniffi-bindgen = "run --package uniffi-bindgen --"
[doc.extern-map.registries]
crates-io = "https://docs.rs/"
[target.'cfg(all())']
# Exclude tarpaulin, android and ios from extra lints since on stable, without
# the nightly-only target-applies-to-host setting at the top, cross compilation
# and otherwise changing cfg's can be very bad for caching. These should never
# be the default either and don't have much target-specific code that would
# benefit from the extra lints.
[target.'cfg(not(any(tarpaulin, target_os = "android", target_os = "ios")))']
rustflags = [
"-Wrust_2018_idioms",
"-Wsemicolon_in_expressions_from_macros",
@@ -35,6 +40,14 @@ rustflags = [
"-Wclippy::todo",
]
[target.'cfg(target_arch = "wasm32")']
rustflags = [
# We have some types that are !Send and/or !Sync only on wasm, it would be
# slightly more efficient, but also pretty annoying, to wrap them in Rc
# where we would use Arc on other platforms.
"-Aclippy::arc_with_non_send_sync",
]
# activate the target-applies-to-host feature.
# Required for `target-applies-to-host` at the top to take effect.
[unstable]
+1 -2
View File
@@ -1,5 +1,4 @@
[profile.default]
retries = 2
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 }
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v3
- uses: actions-rs/audit-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
toolchain: nightly-2023-11-08
components: rustfmt
- name: Run Benchmarks
+18 -151
View File
@@ -18,8 +18,6 @@ concurrency:
env:
CARGO_TERM_COLOR: always
MATRIX_SDK_CRYPTO_NODEJS_PATH: bindings/matrix-sdk-crypto-nodejs
MATRIX_SDK_CRYPTO_JS_PATH: bindings/matrix-sdk-crypto-js
jobs:
xtask:
@@ -35,18 +33,23 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v1
- name: Install protoc
uses: taiki-e/install-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-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
- name: Delete cargo config
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v3
@@ -58,147 +61,6 @@ jobs:
- name: Build library & generate bindings
run: target/debug/xtask ci bindings
lint-js-bindings:
strategy:
fail-fast: true
matrix:
include:
- name: "[m]-crypto-nodejs"
path: "bindings/matrix-sdk-crypto-nodejs"
- name: "[m]-crypto-js"
path: "bindings/matrix-sdk-crypto-js"
name: lint ${{ matrix.name }}
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Node.js
uses: actions/setup-node@v3
- name: Install NPM dependencies
working-directory: ${{ matrix.path }}
run: npm install
- name: run lint
working-directory: ${{ matrix.path }}
run: npm run lint
test-matrix-sdk-crypto-nodejs:
name: ${{ matrix.os-name }} [m]-crypto-nodejs, v${{ matrix.node-version }}
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
# Use Ubuntu LTS-1 for broader glibc compatibility.
os: [ubuntu-20.04]
node-version: [16.0, 18.0, 19.0, 20.0]
include:
- os: ubuntu-20.04
os-name: 🐧
- os: macos-latest
os-name: 🍏
node-version: 20.0
- node-version: 20.0
build-doc: true
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm install
- name: Build the Node.js binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run release-build
- name: Test the Node.js binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run test
# Building in dev-mode and copy lib in failure case
- name: Build the Node.js binding in non-release
if: failure()
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: |
cp *.node release-mode-lib.node
npm run build
- uses: actions/upload-artifact@v3
if: failure()
with:
name: Failure Files
path: |
bindings/matrix-sdk-crypto-nodejs/*.node
/var/crash/*.crash
- if: ${{ matrix.build-doc }}
name: Build the documentation
working-directory: ${{ env.MATRIX_SDK_CRYPTO_NODEJS_PATH }}
run: npm run doc
test-matrix-sdk-crypto-js:
name: 🕸 [m]-crypto-js
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 20.0
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm install
- name: Build the WebAssembly + JavaScript binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run build
- name: Test the JavaScript binding
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run test
- name: Build the documentation
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
run: npm run doc
test-apple:
name: matrix-rust-components-swift
needs: xtask
@@ -210,10 +72,10 @@ jobs:
uses: actions/checkout@v3
# install protoc in case we end up rebuilding opentelemetry-proto
- name: Install Protoc
uses: arduino/setup-protoc@v1
- name: Install protoc
uses: taiki-e/install-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
@@ -221,10 +83,15 @@ jobs:
- name: Install aarch64-apple-ios target
run: rustup target install aarch64-apple-ios
# Cargo config can screw with caching and is only used for alias config
# and extra lints, which we don't care about here
- name: Delete cargo config
run: rm .cargo/config.toml
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v3
+21 -108
View File
@@ -58,7 +58,7 @@ jobs:
# ... but only save the cache on the main branch
# cf https://github.com/Swatinem/rust-cache/issues/95
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -90,7 +90,7 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -122,7 +122,7 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -163,10 +163,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v1
- name: Install protoc
uses: taiki-e/install-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tool: protoc@3.20.3
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@master
@@ -176,7 +176,7 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -184,7 +184,7 @@ jobs:
- name: Test
run: |
cargo nextest run --workspace \
--exclude matrix-sdk-integration-testing --exclude sliding-sync-integration-test --features testing
--exclude matrix-sdk-integration-testing --features testing
- name: Test documentation
run: |
@@ -225,9 +225,6 @@ jobs:
- name: '[m], indexeddb stores, no crypto'
cmd: matrix-sdk-indexeddb-stores-no-crypto
- name: '[m], wasm-example'
cmd: matrix-sdk-command-bot
steps:
- name: Checkout the repo
uses: actions/checkout@v3
@@ -252,7 +249,7 @@ jobs:
# ... but only save the cache on the main branch
# cf https://github.com/Swatinem/rust-cache/issues/95
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
@@ -272,50 +269,6 @@ jobs:
run: |
target/debug/xtask ci wasm-pack ${{ matrix.cmd }}
test-appservice:
name: ${{ matrix.os-name }} [m]-appservice
needs: xtask
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
os-name: 🐧
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-linux }}"
- os: macos-latest
os-name: 🍏
xtask-cachekey: "${{ needs.xtask.outputs.cachekey-macos }}"
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache/restore@v3
with:
path: target/debug/xtask
key: "${{ matrix.xtask-cachekey }}"
fail-on-cache-miss: true
- name: Run checks
run: |
target/debug/xtask ci test-appservice
formatting:
name: Check Formatting
runs-on: ubuntu-latest
@@ -328,7 +281,7 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
toolchain: nightly-2023-11-08
components: rustfmt
- name: Cargo fmt
@@ -345,7 +298,7 @@ jobs:
uses: actions/checkout@v3
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@v1.15.0
uses: crate-ci/typos@v1.17.0
clippy:
name: Run clippy
@@ -357,21 +310,21 @@ jobs:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v1
- name: Install protoc
uses: taiki-e/install-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
toolchain: nightly-2023-11-08
components: clippy
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Get xtask
uses: actions/cache/restore@v3
@@ -390,41 +343,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
- uses: actions/setup-python@v4
with:
python-version: 3.8
- uses: michaelkaye/setup-matrix-synapse@main
with:
uploadLogs: true
httpPort: 8228
disableRateLimiting: true
- name: Test
run: |
cargo nextest run -p matrix-sdk-integration-testing
sliding-sync-integration-tests:
name: Sliding Sync Integration test
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
# run several docker containers with the same networking stack so the hostname 'postgres'
# maps to the postgres container, etc.
services:
@@ -449,7 +367,7 @@ jobs:
# run sliding sync and point it at the postgres container and synapse container.
# the postgres container needs to be above this to make sure it has started prior to this service.
slidingsync:
image: "ghcr.io/matrix-org/sliding-sync:v0.99.2"
image: "ghcr.io/matrix-org/sliding-sync:v0.99.11" # keep in sync with ./coverage.yml
env:
SYNCV3_SERVER: "http://synapse:8008"
SYNCV3_SECRET: "SUPER_CI_SECRET"
@@ -460,14 +378,13 @@ jobs:
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
# latter does not provide networking for services to communicate with it.
synapse:
# Custom image built from https://github.com/matrix-org/synapse/tree/v1.72.0/docker/complement
# with a dummy /complement/ca set
image: ghcr.io/matrix-org/synapse-service:v1.72.0
image: ghcr.io/matrix-org/synapse-service:v1.94.0 # keep in sync with ./coverage.yml
env:
SYNAPSE_COMPLEMENT_DATABASE: sqlite
SERVER_NAME: synapse
ports:
- 8008:8008
steps:
- name: Checkout the repo
uses: actions/checkout@v3
@@ -478,15 +395,11 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install nextest
uses: taiki-e/install-action@nextest
- uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Test
env:
RUST_LOG: "hyper=trace"
@@ -494,4 +407,4 @@ jobs:
HOMESERVER_DOMAIN: "synapse"
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
run: |
cargo nextest run -p sliding-sync-integration-test
cargo nextest run -p matrix-sdk-integration-testing
+87 -36
View File
@@ -17,6 +17,9 @@ concurrency:
env:
CARGO_TERM_COLOR: always
# without matrix_sdk=trace, expressions in `trace!` fields are not evaluated
# when the `trace!` statement is hit, and thus not covered
RUST_LOG: info,matrix_sdk=trace
jobs:
code_coverage:
@@ -24,47 +27,95 @@ jobs:
runs-on: "ubuntu-latest"
if: github.event_name == 'push' || !github.event.pull_request.draft
# run several docker containers with the same networking stack so the hostname 'postgres'
# maps to the postgres container, etc.
services:
# sliding sync needs a postgres container
postgres:
# Docker Hub image
image: postgres
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: syncv3
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
# run sliding sync and point it at the postgres container and synapse container.
# the postgres container needs to be above this to make sure it has started prior to this service.
slidingsync:
image: "ghcr.io/matrix-org/sliding-sync:v0.99.11" # keep in sync with ./ci.yml
env:
SYNCV3_SERVER: "http://synapse:8008"
SYNCV3_SECRET: "SUPER_CI_SECRET"
SYNCV3_BINDADDR: ":8118"
SYNCV3_DB: "user=postgres password=postgres dbname=syncv3 sslmode=disable host=postgres"
ports:
- 8118:8118
# tests need a synapse: this is a service and not michaelkaye/setup-matrix-synapse@main as the
# latter does not provide networking for services to communicate with it.
synapse:
image: ghcr.io/matrix-org/synapse-service:v1.94.0 # keep in sync with ./ci.yml
env:
SYNAPSE_COMPLEMENT_DATABASE: sqlite
SERVER_NAME: synapse
ports:
- 8008:8008
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
# Cargo config can screw with caching and is only used for alias config
# and extra lints, which we don't care about here
- name: Delete cargo config
run: rm .cargo/config.toml
- name: Install tarpaulin
uses: taiki-e/install-action@v2
with:
tool: cargo-tarpaulin
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
# set up backend for integration tests
- uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Install tarpaulin
uses: taiki-e/install-action@v2
with:
tool: cargo-tarpaulin
- uses: gnunicorn/setup-matrix-synapse@main
with:
uploadLogs: true
httpPort: 8228
disableRateLimiting: true
serverName: "matrix-sdk.rs"
# set up backend for integration tests
- uses: actions/setup-python@v4
with:
python-version: 3.8
- name: Run tarpaulin
run: |
cargo tarpaulin --out Xml -e sliding-sync-integration-test --features testing
- name: Run tarpaulin
run: |
rustup run stable cargo tarpaulin \
--skip-clean --profile cov --out xml \
--features experimental-widgets,testing
env:
CARGO_PROFILE_COV_INHERITS: 'dev'
CARGO_PROFILE_COV_DEBUG: 'false'
HOMESERVER_URL: "http://localhost:8008"
HOMESERVER_DOMAIN: "synapse"
SLIDING_SYNC_PROXY_URL: "http://localhost:8118"
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
# Otherwise not required for public repos.
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
# The upload sometimes fails due to https://github.com/codecov/codecov-action/issues/837.
# To make sure that the failure gets flagged clearly in the UI, fail the action.
fail_ci_if_error: true
- name: Upload to codecov.io
uses: codecov/codecov-action@v3
with:
# Work around frequent upload errors, for runs inside the main repo (not PRs from forks).
# Otherwise not required for public repos.
token: ${{ secrets.CODECOV_UPLOAD_TOKEN }}
# The upload sometimes fails due to https://github.com/codecov/codecov-action/issues/837.
# To make sure that the failure gets flagged clearly in the UI, fail the action.
fail_ci_if_error: true
+24 -32
View File
@@ -4,11 +4,21 @@ on:
push:
branches: [main]
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
pages: write
id-token: write
jobs:
docs:
name: All crates
@@ -19,15 +29,15 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install Protoc
uses: arduino/setup-protoc@v1
- name: Install protoc
uses: taiki-e/install-action@v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
tool: protoc@3.20.3
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
toolchain: nightly-2023-11-08
- name: Install Node.js
uses: actions/setup-node@v3
@@ -37,10 +47,10 @@ jobs:
- name: Load cache
uses: Swatinem/rust-cache@v2
with:
save-if: ${{ github.ref == 'refs/head/main' }}
save-if: ${{ github.ref == 'refs/heads/main' }}
# Keep in sync with xtask docs
- name: Build rust documentation
- name: Build documentation
env:
# Work around https://github.com/rust-lang/cargo/issues/10744
CARGO_TARGET_APPLIES_TO_HOST: "true"
@@ -48,31 +58,13 @@ jobs:
run:
cargo doc --no-deps --workspace --features docsrs
- name: Build `matrix-sdk-crypto-nodejs` doc
run: |
cd bindings/matrix-sdk-crypto-nodejs
npm install
npm run build && npm run doc
- name: Build `matrix-sdk-crypto-js` doc
run: |
cd bindings/matrix-sdk-crypto-js
npm install
npm run build && npm run doc
- name: Prepare the doc hierarchy
shell: bash
run: |
mkdir -p doc/bindings/matrix-sdk-crypto-nodejs/
mkdir -p doc/bindings/matrix-sdk-crypto-js/
mv target/doc/* doc/
mv bindings/matrix-sdk-crypto-nodejs/docs/* doc/bindings/matrix-sdk-crypto-nodejs/
mv bindings/matrix-sdk-crypto-js/docs/* doc/bindings/matrix-sdk-crypto-js/
- name: Deploy documentation
- name: Upload artifact
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
uses: actions/upload-pages-artifact@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./doc/
force_orphan: true
path: './target/doc/'
- name: Deploy to GitHub Pages
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: deployment
uses: actions/deploy-pages@v2
@@ -1,119 +0,0 @@
name: Prepare Crypto-Node.js Release
#
# This is a helper workflow to craft a new Node.js release, trigger this via
# the Github Workflow UI by dispatching it manually. Provide the version, the
# matrix-sdk-crypto-nodejs npm package should be set to, and a optionally the
# old version (as used in the git tag) this release should be compared to.
#
# This will then:
# 1. bump the npm version to the one you specified
# 2. commit that change together with the changelog (if it changed, see below)
# 3. create the appropriate tag on that commit
# 4. create the Github draft release, including the changes (if given, see below)
# 5. push these to a new branch, including tag, triggering the `release-crypto-nodejs` workflow
# 6. create a PR to merge these back into `main`
#
# Additionally, if you provide a tag to comapare this tag to, this will:
# 1. create a changelog between the two releases, used for the github release
# 2. update the Changelog.md and include it in the commit
#
# The remaining tasks are done by the release-crypto-nodejs workflow.
on:
workflow_dispatch:
inputs:
version:
description: 'New Node.js SemVer version to create'
required: true
type: string
previous_version:
description: 'Create the changelog by comparing to this old SemVer Version (as used in the tag) '
type: string
env:
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
TAG_PREFIX: "matrix-sdk-crypto-nodejs-v"
jobs:
prepare-release:
name: "Preparing crypto-nodejs release tag"
runs-on: ubuntu-latest
outputs:
tag: "${{ env.TAG_PREFIX }}${{ inputs.version }}"
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
# Generate changelog since last tag, if given
- name: Generate a changelog for upload
if: inputs.previous_version
uses: orhun/git-cliff-action@v1
with:
config: "${{ env.PKG_PATH }}/cliff.toml"
args: --strip header "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
env:
GIT_CLIFF_TAG: "Changes ${{ inputs.previous_version }} -> ${{ inputs.version }}"
GIT_CLIFF_OUTPUT: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
# Update changelog since last tag, if given
- name: Update existing Changelog
if: inputs.previous_version
uses: orhun/git-cliff-action@v1
with:
config: "${{ env.PKG_PATH }}/cliff.toml"
args: "${{env.TAG_PREFIX}}${{ inputs.previous_version }}..HEAD"
env:
GIT_CLIFF_TAG: "${{ inputs.version }}"
GIT_CLIFF_PREPEND: "${{ env.PKG_PATH }}/CHANGELOG.md"
- name: Set version
id: package_version
working-directory: ${{ env.PKG_PATH }}
run: npm version ${{ inputs.version }}
- uses: EndBug/add-and-commit@v9
with:
default_author: github_actions
message: "Tagging Crypto-Node.js for release"
tag: "${{env.TAG_PREFIX}}${{ inputs.version }}"
new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
push: true
add: |
${{ env.PKG_PATH }}/package.json
${{ env.PKG_PATH }}/CHANGELOG.md
# if we have generated changes
- name: Update Github Release notes
if: inputs.previous_version
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
body_path: "${{ env.PKG_PATH }}/CHANGES-${{ inputs.version }}.md"
# no changes, use the default changelog for the body
- name: Update Github Release notes
if: ${{!inputs.previous_version}}
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
# let's create a PR for all this, too
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
body: |
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }}
trigger-release:
# and trigger the tagging release workflow
uses: ./.github/workflows/release-crypto-nodejs.yml
needs: ['prepare-release']
name: "Trigger release Workflow"
with:
tag: ${{needs.prepare-release.outputs.tag}}
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
-139
View File
@@ -1,139 +0,0 @@
name: Release Crypto-Node.js
#
# This workflow releases the crypto-bindings for nodejs
#
# It is triggered when seeing a tag prefixed matching `matrix-sdk-crypto-nodejs-v[0-9]+.*`,
# which then build the native bindings for linux, mac and windows via the CI and uploads
# them to the corresponding Github Release tag. Once they are finished, this workflow will
# package the npm tar.gz and uploads that to the Github Release tag as well, before publishing
# it to npmjs.com automatically.
#
# The usual way to trigger this is by manually triggering the `prep-crypto-nodejs-release`
# workflow. See its documentation for instructions how to use it.
env:
PKG_PATH: "bindings/matrix-sdk-crypto-nodejs"
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: 'aarch64-linux-gnu-gcc'
CARGO_TARGET_I686_UNKNOWN_LINUX_GNU_LINKER: 'i686-linux-gnu-gcc'
CARGO_TARGET_ARM_UNKNOWN_LINUX_GNUEABIHF_LINKER: 'arm-linux-gnueabihf-gcc'
on:
push:
tags:
- matrix-sdk-crypto-nodejs-v[0-9]+.*
workflow_call:
inputs:
tag:
description: "The tag to build with"
required: true
type: string
secrets:
NPM_TOKEN:
required: true
jobs:
upload-assets:
name: "Upload prebuilt libraries"
strategy:
fail-fast: false
matrix:
include:
# ----------------------------------- Linux
# Use Ubuntu LTS-1 for broader glibc compatibility.
- target: x86_64-unknown-linux-gnu
os: ubuntu-20.04
- target: i686-unknown-linux-gnu
apt_install: gcc-i686-linux-gnu g++-i686-linux-gnu
os: ubuntu-20.04
- target: aarch64-unknown-linux-gnu
os: ubuntu-20.04
apt_install: gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
- target: arm-unknown-linux-gnueabihf
os: ubuntu-20.04
apt_install: gcc-arm-linux-gnueabihf
- target: x86_64-unknown-linux-musl
os: ubuntu-20.04
apt_install: musl-tools
# ----------------------------------- macOS
- target: aarch64-apple-darwin
os: macos-latest
- target: x86_64-apple-darwin
os: macos-latest
# ----------------------------------- Windows
- target: x86_64-pc-windows-msvc
os: windows-latest
- target: i686-pc-windows-msvc
os: windows-latest
- target: aarch64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
# use the given tag
- uses: actions/checkout@v3
name: "Checking out ${{ inputs.tag }}"
if: "${{ inputs.tag }}"
with:
ref: ${{ inputs.tag }}
# use the default
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
targets: ${{ matrix.target }}
- name: Install Node.js
uses: actions/setup-node@v3
- if: ${{ matrix.apt_install }}
run: |
sudo apt-get update
sudo apt-get install -y ${{ matrix.apt_install }}
- name: Build lib
working-directory: ${{env.PKG_PATH}}
run: |
npm install --ignore-scripts
npx napi build --platform --release --strip --target ${{ matrix.target }}
- name: Upload artifacts to release
uses: softprops/action-gh-release@v1
with:
draft: true
files: ${{env.PKG_PATH}}/*.node
publish-nodejs-package:
name: "Package nodejs package"
runs-on: ubuntu-latest
needs:
- upload-assets
steps:
# use the given tag
- uses: actions/checkout@v3
name: "Checking out ${{ inputs.tag }}"
if: "${{ inputs.tag }}"
with:
ref: ${{ inputs.tag }}
# use the default
- uses: actions/checkout@v3
if: "${{ !inputs.tag }}"
- name: Install Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: nightly-2023-05-06
- name: Install Node.js
uses: actions/setup-node@v3
- name: Build lib
working-directory: ${{env.PKG_PATH}}
run: |
npm install --ignore-scripts
npm run build
npm pack
- name: Upload npm package to release
uses: softprops/action-gh-release@v1
with:
draft: true
files: ${{env.PKG_PATH}}/*tgz
- name: Publish to npmjs.com
uses: JS-DevTools/npm-publish@v1
with:
package: ${{env.PKG_PATH}}/package.json
access: public
token: ${{ secrets.NPM_TOKEN }}
-56
View File
@@ -1,56 +0,0 @@
# This workflow releases the `matrix-sdk-crypto-js` project.
#
# It is triggered when a new tag appears that matches
# `matrix-sdk-crypto-js-[0-9]+.*`. This workflow builds the package
# for the bindings, runs its tests to ensure everything is still
# correct, and publishes the package on NPM and on a newly created
# Github release.
name: Release `crypto-js`
env:
CARGO_TERM_COLOR: always
PKG_PATH: "bindings/matrix-sdk-crypto-js"
on:
push:
tags:
- matrix-sdk-crypto-js-[0-9]+.*
jobs:
publish-matrix-sdk-crypto-js:
name: Publish 🕸 [m]-crypto-js
runs-on: ubuntu-latest
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 20.0
- name: Install NPM dependencies
working-directory: ${{ env.PKG_PATH }}
run: npm install
- name: Publish the WebAssembly + JavaScript binding (imply building + testing)
uses: JS-DevTools/npm-publish@v1
with:
package: ${{env.PKG_PATH}}/package.json
access: public
token: ${{ secrets.NPM_TOKEN }}
- name: Create the Github release
uses: softprops/action-gh-release@v1
with:
draft: true
files: ${{ env.PKG_PATH }}/pkg/matrix-org-matrix-sdk-crypto-js-*.tgz
+15 -1
View File
@@ -13,7 +13,21 @@ extend-ignore-re = [
"\\b[a-z0-9+/=][A-Za-z0-9+/=]{7,}[a-z0-9+/=][A-Z]\\b",
]
[default.extend-identifiers]
WeeChat = "WeeChat"
# all of these are valid words, but should never appear in this repo
[default.extend-words]
sing = "sign"
singed = "signed"
singing = "signing"
Nd = "Nd"
[files]
# Our json files contain a bunch of base64 encoded ed25519 keys which aren't
# automatically ignored, we ignore them here.
extend-exclude = ["*.json"]
extend-exclude = [
"*.json",
# We are using some fuzzy match patterns that can be understood as typos confusingly.
"crates/matrix-sdk-ui/tests/integration/room_list_service.rs",
]
Generated
+2330 -1611
View File
File diff suppressed because it is too large Load Diff
+36 -20
View File
@@ -2,17 +2,14 @@
members = [
"benchmarks",
"bindings/matrix-sdk-crypto-ffi",
"bindings/matrix-sdk-crypto-js",
"bindings/matrix-sdk-crypto-nodejs",
"bindings/matrix-sdk-ffi",
"crates/*",
"testing/*",
"examples/*",
"labs/*",
"uniffi-bindgen",
"xtask",
]
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
# xtask, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*"]
resolver = "2"
@@ -21,34 +18,52 @@ rust-version = "1.70"
[workspace.dependencies]
anyhow = "1.0.68"
assert-json-diff = "2"
assert_matches = "1.5.0"
assert_matches2 = "0.1.1"
async-rx = "0.1.3"
async-stream = "0.3.3"
async-trait = "0.1.60"
as_variant = "1.2.0"
base64 = "0.21.0"
byteorder = "1.4.3"
ctor = "0.2.0"
dashmap = "5.2.0"
eyeball = "0.7.0"
eyeball-im = "0.2.0"
eyeball-im-util = "0.2.1"
eyeball = { version = "0.8.7", features = ["tracing"] }
eyeball-im = { version = "0.4.1", features = ["tracing"] }
eyeball-im-util = "0.5.1"
futures-core = "0.3.28"
futures-executor = "0.3.21"
futures-util = { version = "0.3.26", default-features = false, features = ["alloc"] }
http = "0.2.6"
ruma = { git = "https://github.com/ruma/ruma", rev = "cf32036df4c9daca736dcd7f0d9d65debcf9897f", features = ["client-api-c", "compat-user-id"] }
ruma-common = { git = "https://github.com/ruma/ruma", rev = "cf32036df4c9daca736dcd7f0d9d65debcf9897f" }
itertools = "0.12.0"
ruma = { version = "0.9.3", features = ["client-api-c", "compat-upload-signatures", "compat-user-id", "compat-arbitrary-length-ids", "unstable-msc3401"] }
ruma-common = "0.12.0"
once_cell = "1.16.0"
rand = "0.8.5"
serde = "1.0.151"
serde_html_form = "0.2.0"
serde_json = "1.0.91"
sha2 = "0.10.8"
stream_assert = "0.1.1"
thiserror = "1.0.38"
tokio = { version = "1.24", default-features = false, features = ["sync"] }
tracing = { version = "0.1.36", default-features = false, features = ["std"] }
tracing-core = "0.1.30"
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "a123d11c7aede132c4210a69c2537ba7ceac3b24" }
uniffi_bindgen = { git = "https://github.com/mozilla/uniffi-rs", rev = "a123d11c7aede132c4210a69c2537ba7ceac3b24" }
vodozemac = "0.4.0"
zeroize = "1.3.0"
tokio = { version = "1.30.0", default-features = false, features = ["sync"] }
tokio-stream = "0.1.14"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-core = "0.1.32"
uniffi = { version = "0.25.3", git = "https://github.com/mozilla/uniffi-rs", rev = "0d58c94cbd2ef63554f3388d03d55984be76bb1f" }
uniffi_bindgen = { version = "0.25.3", git = "https://github.com/mozilla/uniffi-rs", rev = "0d58c94cbd2ef63554f3388d03d55984be76bb1f" }
vodozemac = "0.5.0"
zeroize = "1.6.0"
matrix-sdk = { path = "crates/matrix-sdk", version = "0.7.0", default-features = false }
matrix-sdk-base = { path = "crates/matrix-sdk-base", version = "0.7.0" }
matrix-sdk-common = { path = "crates/matrix-sdk-common", version = "0.7.0" }
matrix-sdk-crypto = { path = "crates/matrix-sdk-crypto", version = "0.7.0" }
matrix-sdk-indexeddb = { path = "crates/matrix-sdk-indexeddb", version = "0.7.0", default-features = false }
matrix-sdk-qrcode = { path = "crates/matrix-sdk-qrcode", version = "0.7.0" }
matrix-sdk-sqlite = { path = "crates/matrix-sdk-sqlite", version = "0.7.0", default-features = false }
matrix-sdk-store-encryption = { path = "crates/matrix-sdk-store-encryption", version = "0.7.0" }
matrix-sdk-test = { path = "testing/matrix-sdk-test", version = "0.7.0" }
matrix-sdk-ui = { path = "crates/matrix-sdk-ui", version = "0.7.0", default-features = false }
# Default release profile, select with `--release`
[profile.release]
@@ -74,8 +89,9 @@ debug = 2
# Custom profile for use in (debug) builds of the binding crates, use
# `--profile reldbg` to select
[profile.reldbg]
inherits = "dev"
inherits = "dbg"
opt-level = 3
[patch.crates-io]
async-compat = { git = "https://github.com/jplatte/async-compat", rev = "b83b197622d8b5756d3594ce73b720896b07faae" }
async-compat = { git = "https://github.com/jplatte/async-compat", rev = "16dc8597ec09a6102d58d4e7b67714a35dd0ecb8" }
const_panic = { git = "https://github.com/jplatte/const_panic", rev = "9024a4cb3eac45c1d2d980f17aaee287b17be498" }
+1 -1
View File
@@ -61,7 +61,7 @@ You can stay informed about updates on the access token by listening to `client.
## Quick Troubleshooting
You find yourself focussed with any of these, here are the steps to follow to upgrade your code accordingly:
You find yourself focused with any of these, here are the steps to follow to upgrade your code accordingly:
### warning: use of deprecated associated function `matrix_sdk::Client::register_event_handler`: Use [`Client::add_event_handler`](#method.add_event_handler) instead
+7 -7
View File
@@ -8,19 +8,19 @@ version = "1.0.0"
publish = false
[dependencies]
criterion = { version = "0.4.0", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-base = { path = "../crates/matrix-sdk-base" }
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
matrix-sdk-sqlite = { path = "../crates/matrix-sdk-sqlite", version = "0.1.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
matrix-sdk = { path = "../crates/matrix-sdk" }
criterion = { version = "0.5.1", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-base = { workspace = true }
matrix-sdk-crypto = { workspace = true }
matrix-sdk-sqlite = { workspace = true, features = ["crypto-store"] }
matrix-sdk-test = { workspace = true }
matrix-sdk = { workspace = true }
ruma = { workspace = true }
serde_json = { workspace = true }
tempfile = "3.3.0"
tokio = { version = "1.24.2", default-features = false, features = ["rt-multi-thread"] }
[target.'cfg(target_os = "linux")'.dependencies]
pprof = { version = "0.11.0", features = ["flamegraph", "criterion"] }
pprof = { version = "0.13.0", features = ["flamegraph", "criterion"] }
[[bench]]
name = "crypto_bench"
+4 -4
View File
@@ -1,6 +1,6 @@
use std::{ops::Deref, sync::Arc};
use criterion::*;
use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion, Throughput};
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
use matrix_sdk_sqlite::SqliteCryptoStore;
use matrix_sdk_test::response_from_file;
@@ -30,7 +30,7 @@ fn keys_query_response() -> get_keys::v3::Response {
let data: Value = serde_json::from_slice(data).unwrap();
let data = response_from_file(&data);
get_keys::v3::Response::try_from_http_response(data)
.expect("Can't parse the keys upload response")
.expect("Can't parse the `/keys/upload` response")
}
fn keys_claim_response() -> claim_keys::v3::Response {
@@ -38,7 +38,7 @@ fn keys_claim_response() -> claim_keys::v3::Response {
let data: Value = serde_json::from_slice(data).unwrap();
let data = response_from_file(&data);
claim_keys::v3::Response::try_from_http_response(data)
.expect("Can't parse the keys upload response")
.expect("Can't parse the `/keys/upload` response")
}
fn huge_keys_query_response() -> get_keys::v3::Response {
@@ -46,7 +46,7 @@ fn huge_keys_query_response() -> get_keys::v3::Response {
let data: Value = serde_json::from_slice(data).unwrap();
let data = response_from_file(&data);
get_keys::v3::Response::try_from_http_response(data)
.expect("Can't parse the keys query response")
.expect("Can't parse the `/keys/query` response")
}
pub fn keys_query(c: &mut Criterion) {
+7 -5
View File
@@ -1,7 +1,9 @@
use criterion::*;
use std::sync::Arc;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use matrix_sdk::{
config::StoreConfig,
matrix_auth::{Session, SessionTokens},
matrix_auth::{MatrixSession, MatrixSessionTokens},
Client, RoomInfo, RoomState, StateChanges,
};
use matrix_sdk_base::{store::MemoryStore, SessionMeta, StateStore as _};
@@ -44,12 +46,12 @@ pub fn restore_session(c: &mut Criterion) {
changes.add_room(RoomInfo::new(&room_id, RoomState::Invited));
}
let session = Session {
let session = MatrixSession {
meta: SessionMeta {
user_id: user_id!("@somebody:example.com").to_owned(),
device_id: device_id!("DEVICE_ID").to_owned(),
},
tokens: SessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
tokens: MatrixSessionTokens { access_token: "OHEY".to_owned(), refresh_token: None },
};
// Start the benchmark.
@@ -60,7 +62,7 @@ pub fn restore_session(c: &mut Criterion) {
const NAME: &str = "restore a session";
// Memory
let mem_store = MemoryStore::new();
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| {
+11 -6
View File
@@ -7,19 +7,24 @@ maintained by the owners of the Matrix Rust SDK project.
[`matrix-sdk`] crate via [`matrix-sdk-ffi`],
* [`matrix-sdk-crypto-ffi`], UniFFI (Kotlin, Swift, Python, Ruby) bindings of the [`matrix-sdk-crypto`]
crate,
* [`matrix-sdk-ffi`], UniFFI bindings of the [`matrix-sdk`] crate.
There are also external bindings in other repositories:
* [`matrix-sdk-crypto-js`], JavaScript bindings of the
[`matrix-sdk-crypto`] crate,
* [`matrix-sdk-crypto-nodejs`], Node.js bindings of the
[`matrix-sdk-crypto`] crate,
* [`matrix-sdk-ffi`], UniFFI bindings of the [`matrix-sdk`] crate.
[`matrix-sdk-crypto`] crate
[`apple`]: ./apple
[`matrix-sdk-crypto-ffi`]: ./matrix-sdk-crypto-ffi
[`matrix-sdk-crypto-js`]: ./matrix-sdk-crypto-js
[`matrix-sdk-crypto-nodejs`]: ./matrix-sdk-crypto-nodejs
[`matrix-sdk-crypto`]: ../crates/matrix-sdk-crypto
[`matrix-sdk-ffi`]: ./matrix-sdk-ffi
[`matrix-sdk`]: ../crates/matrix-sdk
# Contributing
To contribute read this [guide](./CONTRIBUTING.md).
[`matrix-sdk-crypto-js`]: https://github.com/matrix-org/matrix-rust-sdk-crypto-web
[`matrix-sdk-crypto-nodejs`]: https://github.com/matrix-org/matrix-rust-sdk-crypto-nodejs
## Contributing
To contribute read this [guide](./CONTRIBUTING.md).
+13 -21
View File
@@ -18,18 +18,18 @@ bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
[dependencies]
anyhow = { workspace = true }
base64 = { workspace = true }
futures-util = "0.3.25"
futures-util = "0.3.28"
hmac = "0.12.1"
http = { workspace = true }
pbkdf2 = "0.11.0"
rand = "0.8.5"
matrix-sdk-common = { workspace = true }
pbkdf2 = "0.12.2"
rand = { workspace = true }
ruma = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sha2 = "0.10.2"
sha2 = { workspace = true }
thiserror = { workspace = true }
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
uniffi = { workspace = true }
vodozemac = { workspace = true }
@@ -39,31 +39,23 @@ zeroize = { workspace = true, features = ["zeroize_derive"] }
version = "0.2.2"
features = ["lax_deserialize"]
[dependencies.matrix-sdk-common]
path = "../../crates/matrix-sdk-common"
version = "0.6.0"
[dependencies.matrix-sdk-crypto]
path = "../../crates/matrix-sdk-crypto"
version = "0.6.0"
default_features = false
features = ["qrcode", "backups_v1", "automatic-room-key-forwarding"]
workspace = true
features = ["qrcode", "automatic-room-key-forwarding"]
[dependencies.matrix-sdk-sqlite]
path = "../../crates/matrix-sdk-sqlite"
version = "0.1.0"
default_features = false
workspace = true
features = ["crypto-store"]
[dependencies.tokio]
version = "1.24.2"
version = "1.33.0"
default_features = false
features = ["rt-multi-thread"]
[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
vergen = { version = "8.0.0", features = ["build", "git", "gitcl"] }
vergen = { version = "8.2.5", features = ["build", "git", "gitcl"] }
[dev-dependencies]
tempfile = "3.3.0"
assert_matches = { workspace = true }
tempfile = "3.8.0"
assert_matches2 = { workspace = true }
+29 -1
View File
@@ -1,8 +1,36 @@
use std::error::Error;
use std::{env, error::Error};
use vergen::EmitBuilder;
/// 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.
/// The workaround comes from: https://github.com/mozilla/application-services/pull/5442
fn setup_x86_64_android_workaround() {
let target_os = env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set");
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").expect("CARGO_CFG_TARGET_ARCH not set");
if target_arch == "x86_64" && target_os == "android" {
let android_ndk_home = env::var("ANDROID_NDK_HOME").expect("ANDROID_NDK_HOME not set");
let build_os = match env::consts::OS {
"linux" => "linux",
"macos" => "darwin",
"windows" => "windows",
_ => panic!(
"Unsupported OS. You must use either Linux, MacOS or Windows to build the crate."
),
};
const DEFAULT_CLANG_VERSION: &str = "14.0.7";
let clang_version =
env::var("NDK_CLANG_VERSION").unwrap_or_else(|_| DEFAULT_CLANG_VERSION.to_owned());
let linux_x86_64_lib_dir = format!(
"toolchains/llvm/prebuilt/{build_os}-x86_64/lib64/clang/{clang_version}/lib/linux/"
);
println!("cargo:rustc-link-search={android_ndk_home}/{linux_x86_64_lib_dir}");
println!("cargo:rustc-link-lib=static=clang_rt.builtins-x86_64-android");
}
}
fn main() -> Result<(), Box<dyn Error>> {
setup_x86_64_android_workaround();
uniffi::generate_scaffolding("./src/olm.udl")?;
EmitBuilder::builder().git_sha(true).git_describe(true, false, None).emit()?;
@@ -3,7 +3,7 @@ use std::{collections::HashMap, iter, ops::DerefMut, sync::Arc};
use hmac::Hmac;
use matrix_sdk_crypto::{
backups::DecryptionError,
store::{CryptoStoreError as InnerStoreError, RecoveryKey},
store::{BackupDecryptionKey, CryptoStoreError as InnerStoreError},
};
use pbkdf2::pbkdf2;
use rand::{distributions::Alphanumeric, thread_rng, Rng};
@@ -14,7 +14,7 @@ use zeroize::Zeroize;
/// The private part of the backup key, the one used for recovery.
#[derive(uniffi::Object)]
pub struct BackupRecoveryKey {
pub(crate) inner: RecoveryKey,
pub(crate) inner: BackupDecryptionKey,
pub(crate) passphrase_info: Option<PassphraseInfo>,
}
@@ -76,7 +76,7 @@ impl BackupRecoveryKey {
#[uniffi::constructor]
pub fn new() -> Arc<Self> {
Arc::new(Self {
inner: RecoveryKey::new()
inner: BackupDecryptionKey::new()
.expect("Can't gather enough randomness to create a recovery key"),
passphrase_info: None,
})
@@ -85,13 +85,13 @@ impl BackupRecoveryKey {
/// Try to create a [`BackupRecoveryKey`] from a base 64 encoded string.
#[uniffi::constructor]
pub fn from_base64(key: String) -> Result<Arc<Self>, DecodeError> {
Ok(Arc::new(Self { inner: RecoveryKey::from_base64(&key)?, passphrase_info: None }))
Ok(Arc::new(Self { inner: BackupDecryptionKey::from_base64(&key)?, passphrase_info: None }))
}
/// Try to create a [`BackupRecoveryKey`] from a base 58 encoded string.
#[uniffi::constructor]
pub fn from_base58(key: String) -> Result<Arc<Self>, DecodeError> {
Ok(Arc::new(Self { inner: RecoveryKey::from_base58(&key)?, passphrase_info: None }))
Ok(Arc::new(Self { inner: BackupDecryptionKey::from_base58(&key)?, passphrase_info: None }))
}
/// Create a new [`BackupRecoveryKey`] from the given passphrase.
@@ -113,14 +113,18 @@ impl BackupRecoveryKey {
let mut key = Box::new([0u8; Self::KEY_SIZE]);
let rounds = rounds as u32;
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut());
pbkdf2::<Hmac<Sha512>>(passphrase.as_bytes(), salt.as_bytes(), rounds, key.deref_mut())
.expect(
"We should be able to expand a passphrase of any length due to \
HMAC being able to be initialized with any input size",
);
let recovery_key = RecoveryKey::from_bytes(&key);
let backup_decryption_key = BackupDecryptionKey::from_bytes(&key);
key.zeroize();
Arc::new(Self {
inner: recovery_key,
inner: backup_decryption_key,
passphrase_info: Some(PassphraseInfo {
private_key_salt: salt,
private_key_iterations: rounds as i32,
@@ -145,7 +149,22 @@ impl BackupRecoveryKey {
let signatures: HashMap<String, HashMap<String, String>> = public_key
.signatures()
.into_iter()
.map(|(k, v)| (k.to_string(), v.into_iter().map(|(k, v)| (k.to_string(), v)).collect()))
.map(|(k, v)| {
(
k.to_string(),
v.into_iter()
.map(|(k, v)| {
(
k.to_string(),
match v {
Ok(s) => s.to_base64(),
Err(s) => s.source,
},
)
})
.collect(),
)
})
.collect();
MegolmV1BackupKey {
@@ -169,11 +188,11 @@ impl BackupRecoveryKey {
}
#[cfg(test)]
mod test {
mod tests {
use ruma::api::client::backup::KeyBackupData;
use serde_json::json;
use super::*;
use super::BackupRecoveryKey;
#[test]
fn test_decrypt_key() {
@@ -0,0 +1,191 @@
use std::{mem::ManuallyDrop, sync::Arc};
use matrix_sdk_crypto::dehydrated_devices::{
DehydratedDevice as InnerDehydratedDevice, DehydratedDevices as InnerDehydratedDevices,
RehydratedDevice as InnerRehydratedDevice,
};
use ruma::{api::client::dehydrated_device, events::AnyToDeviceEvent, serde::Raw, OwnedDeviceId};
use serde_json::json;
use tokio::runtime::Handle;
use zeroize::Zeroize;
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum DehydrationError {
#[error(transparent)]
Pickle(#[from] matrix_sdk_crypto::vodozemac::LibolmPickleError),
#[error(transparent)]
MissingSigningKey(#[from] matrix_sdk_crypto::SignatureError),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error(transparent)]
Store(#[from] matrix_sdk_crypto::CryptoStoreError),
#[error("The pickle key has an invalid length, expected 32 bytes, got {0}")]
PickleKeyLength(usize),
}
impl From<matrix_sdk_crypto::dehydrated_devices::DehydrationError> for DehydrationError {
fn from(value: matrix_sdk_crypto::dehydrated_devices::DehydrationError) -> Self {
match value {
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Json(e) => Self::Json(e),
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Pickle(e) => Self::Pickle(e),
matrix_sdk_crypto::dehydrated_devices::DehydrationError::MissingSigningKey(e) => {
Self::MissingSigningKey(e)
}
matrix_sdk_crypto::dehydrated_devices::DehydrationError::Store(e) => Self::Store(e),
}
}
}
#[derive(uniffi::Object)]
pub struct DehydratedDevices {
pub(crate) runtime: Handle,
pub(crate) inner: ManuallyDrop<InnerDehydratedDevices>,
}
impl Drop for DehydratedDevices {
fn drop(&mut self) {
// See the drop implementation for the `crate::OlmMachine` for an explanation.
let _guard = self.runtime.enter();
unsafe {
ManuallyDrop::drop(&mut self.inner);
}
}
}
#[uniffi::export]
impl DehydratedDevices {
pub fn create(&self) -> Result<Arc<DehydratedDevice>, DehydrationError> {
let inner = self.runtime.block_on(self.inner.create())?;
Ok(Arc::new(DehydratedDevice {
inner: ManuallyDrop::new(inner),
runtime: self.runtime.to_owned(),
}))
}
pub fn rehydrate(
&self,
pickle_key: Vec<u8>,
device_id: String,
device_data: String,
) -> Result<Arc<RehydratedDevice>, DehydrationError> {
let device_data: Raw<_> = serde_json::from_str(&device_data)?;
let device_id: OwnedDeviceId = device_id.into();
let mut key = get_pickle_key(&pickle_key)?;
let ret = RehydratedDevice {
runtime: self.runtime.to_owned(),
inner: ManuallyDrop::new(self.runtime.block_on(self.inner.rehydrate(
&key,
&device_id,
device_data,
))?),
}
.into();
key.zeroize();
Ok(ret)
}
}
#[derive(uniffi::Object)]
pub struct RehydratedDevice {
inner: ManuallyDrop<InnerRehydratedDevice>,
runtime: Handle,
}
impl Drop for RehydratedDevice {
fn drop(&mut self) {
// See the drop implementation for the `crate::OlmMachine` for an explanation.
let _guard = self.runtime.enter();
unsafe {
ManuallyDrop::drop(&mut self.inner);
}
}
}
#[uniffi::export]
impl RehydratedDevice {
pub fn receive_events(&self, events: String) -> Result<(), crate::CryptoStoreError> {
let events: Vec<Raw<AnyToDeviceEvent>> = serde_json::from_str(&events)?;
self.runtime.block_on(self.inner.receive_events(events))?;
Ok(())
}
}
#[derive(uniffi::Object)]
pub struct DehydratedDevice {
pub(crate) runtime: Handle,
pub(crate) inner: ManuallyDrop<InnerDehydratedDevice>,
}
impl Drop for DehydratedDevice {
fn drop(&mut self) {
// See the drop implementation for the `crate::OlmMachine` for an explanation.
let _guard = self.runtime.enter();
unsafe {
ManuallyDrop::drop(&mut self.inner);
}
}
}
#[uniffi::export]
impl DehydratedDevice {
pub fn keys_for_upload(
&self,
device_display_name: String,
pickle_key: Vec<u8>,
) -> Result<UploadDehydratedDeviceRequest, DehydrationError> {
let mut key = get_pickle_key(&pickle_key)?;
let request =
self.runtime.block_on(self.inner.keys_for_upload(device_display_name, &key))?;
key.zeroize();
Ok(request.into())
}
}
#[derive(Debug, uniffi::Record)]
pub struct UploadDehydratedDeviceRequest {
/// The serialized JSON body of the request.
body: String,
}
impl From<dehydrated_device::put_dehydrated_device::unstable::Request>
for UploadDehydratedDeviceRequest
{
fn from(value: dehydrated_device::put_dehydrated_device::unstable::Request) -> Self {
let body = json!({
"device_id": value.device_id,
"device_data": value.device_data,
"initial_device_display_name": value.initial_device_display_name,
"device_keys": value.device_keys,
"one_time_keys": value.one_time_keys,
"fallback_keys": value.fallback_keys,
});
let body = serde_json::to_string(&body)
.expect("We should be able to serialize the PUT dehydrated devices request body");
Self { body }
}
}
fn get_pickle_key(pickle_key: &[u8]) -> Result<Box<[u8; 32]>, DehydrationError> {
let pickle_key_length = pickle_key.len();
if pickle_key_length == 32 {
let mut key = Box::new([0u8; 32]);
key.copy_from_slice(pickle_key);
Ok(key)
} else {
Err(DehydrationError::PickleKeyLength(pickle_key_length))
}
}
+11 -7
View File
@@ -59,12 +59,17 @@ pub enum CryptoStoreError {
Identifier(#[from] IdParseError),
}
#[derive(Debug, uniffi::Error)]
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum DecryptionError {
#[error("serialization error: {error}")]
Serialization { error: String },
#[error("identifier parsing error: {error}")]
Identifier { error: String },
#[error("megolm error: {error}")]
Megolm { error: String },
#[error("missing room key: {error}")]
MissingRoomKey { error: String, withheld_code: Option<String> },
#[error("store error: {error}")]
Store { error: String },
}
@@ -100,10 +105,10 @@ impl From<InnerStoreError> for DecryptionError {
#[cfg(test)]
mod tests {
use assert_matches2::assert_let;
use matrix_sdk_crypto::MegolmError;
use assert_matches::assert_matches;
use super::*;
use super::DecryptionError;
#[test]
fn test_withheld_error_mapping() {
@@ -113,9 +118,8 @@ mod tests {
let binding_error: DecryptionError = inner_error.into();
let code = assert_matches!(
binding_error,
DecryptionError::MissingRoomKey { error: _, withheld_code: Some(withheld_code) } => withheld_code
assert_let!(
DecryptionError::MissingRoomKey { error: _, withheld_code: Some(code) } = binding_error
);
assert_eq!("m.unverified", code)
}
+15 -10
View File
@@ -7,6 +7,7 @@
#![allow(unused_qualifications)]
mod backup_recovery_key;
mod dehydrated_devices;
mod device;
mod error;
mod logger;
@@ -32,7 +33,7 @@ use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use matrix_sdk_crypto::{
backups::SignatureState,
olm::{IdentityKeys, InboundGroupSession, Session},
store::{Changes, CryptoStore, RoomSettings as RustRoomSettings},
store::{Changes, CryptoStore, PendingChanges, RoomSettings as RustRoomSettings},
types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey},
EncryptionSettings as RustEncryptionSettings, LocalTrust,
};
@@ -210,7 +211,7 @@ async fn migrate_data(
passphrase: Option<String>,
progress_listener: Box<dyn ProgressListener>,
) -> anyhow::Result<()> {
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::RecoveryKey};
use matrix_sdk_crypto::{olm::PrivateCrossSigningIdentity, store::BackupDecryptionKey};
use vodozemac::olm::Account;
use zeroize::Zeroize;
@@ -247,7 +248,7 @@ async fn migrate_data(
uploaded_signed_key_count: data.account.uploaded_signed_key_count as u64,
creation_local_time: MilliSecondsSinceUnixEpoch(UInt::default()),
};
let account = matrix_sdk_crypto::olm::ReadOnlyAccount::from_pickle(pickled_account)?;
let account = matrix_sdk_crypto::olm::Account::from_pickle(pickled_account)?;
processed_steps += 1;
listener(processed_steps, total_steps);
@@ -264,8 +265,10 @@ async fn migrate_data(
data.inbound_group_sessions,
)?;
let recovery_key =
data.backup_recovery_key.map(|k| RecoveryKey::from_base58(k.as_str())).transpose()?;
let backup_decryption_key = data
.backup_recovery_key
.map(|k| BackupDecryptionKey::from_base58(k.as_str()))
.transpose()?;
let cross_signing = PrivateCrossSigningIdentity::empty((*user_id).into());
cross_signing
@@ -301,12 +304,13 @@ async fn migrate_data(
room_settings.insert(room_id, settings.into());
}
store.save_pending_changes(PendingChanges { account: Some(account) }).await?;
let changes = Changes {
account: Some(account),
private_identity: Some(cross_signing),
sessions,
inbound_group_sessions,
recovery_key,
backup_decryption_key,
backup_version: data.backup_version,
room_settings,
..Default::default()
@@ -456,7 +460,7 @@ fn collect_sessions(
.signing_key
.into_iter()
.map(|(k, v)| {
let algorithm = DeviceKeyAlgorithm::try_from(k)?;
let algorithm = DeviceKeyAlgorithm::from(k);
let key = SigningKey::from_parts(&algorithm, v)?;
Ok((algorithm, key))
@@ -522,6 +526,7 @@ pub fn migrate_room_settings(
}
/// Callback that will be passed over the FFI to report progress
#[uniffi::export(callback_interface)]
pub trait ProgressListener {
/// The callback that should be called on the Rust side
///
@@ -765,7 +770,7 @@ impl TryFrom<matrix_sdk_crypto::store::BackupKeys> for BackupKeys {
fn try_from(keys: matrix_sdk_crypto::store::BackupKeys) -> Result<Self, Self::Error> {
Ok(Self {
recovery_key: BackupRecoveryKey {
inner: keys.recovery_key.ok_or(())?,
inner: keys.decryption_key.ok_or(())?,
passphrase_info: None,
}
.into(),
@@ -879,7 +884,7 @@ fn vodozemac_version() -> String {
uniffi::include_scaffolding!("olm");
#[cfg(test)]
mod test {
mod tests {
use anyhow::Result;
use serde_json::{json, Value};
use tempfile::tempdir;
@@ -7,6 +7,7 @@ use tracing_subscriber::{fmt::MakeWriter, EnvFilter};
/// Trait that can be used to forward Rust logs over FFI to a language specific
/// logger.
#[uniffi::export(callback_interface)]
pub trait Logger: Send {
/// Called every time the Rust side wants to post a log line.
fn log(&self, log_line: String);
+108 -27
View File
@@ -16,7 +16,7 @@ use matrix_sdk_crypto::{
},
decrypt_room_key_export, encrypt_room_key_export,
olm::ExportedRoomKey,
store::{Changes, RecoveryKey},
store::{BackupDecryptionKey, Changes},
LocalTrust, OlmMachine as InnerMachine, UserIdentities,
};
use ruma::{
@@ -48,6 +48,7 @@ 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},
@@ -58,6 +59,51 @@ use crate::{
Sas, SignatureUploadRequest, StartSasResult, UserIdentity, Verification, VerificationRequest,
};
/// The return value for the [`OlmMachine::receive_sync_changes()`] method.
///
/// Will contain various information about the `/sync` changes the
/// [`OlmMachine`] processed.
#[derive(uniffi::Record)]
pub struct SyncChangesResult {
/// The, now possibly decrypted, to-device events the [`OlmMachine`]
/// received, decrypted, and processed.
to_device_events: Vec<String>,
/// Information about the room keys that were extracted out of the to-device
/// events.
room_key_infos: Vec<RoomKeyInfo>,
}
/// Information on a room key that has been received or imported.
#[derive(uniffi::Record)]
pub struct RoomKeyInfo {
/// The [messaging algorithm] that this key is used for. Will be one of the
/// `m.megolm.*` algorithms.
///
/// [messaging algorithm]: https://spec.matrix.org/v1.6/client-server-api/#messaging-algorithms
pub algorithm: String,
/// The room where the key is used.
pub room_id: String,
/// The Curve25519 key of the device which initiated the session originally.
pub sender_key: String,
/// The ID of the session that the key is for.
pub session_id: String,
}
impl From<matrix_sdk_crypto::store::RoomKeyInfo> for RoomKeyInfo {
fn from(value: matrix_sdk_crypto::store::RoomKeyInfo) -> Self {
Self {
algorithm: value.algorithm.to_string(),
room_id: value.room_id.to_string(),
sender_key: value.sender_key.to_base64(),
session_id: value.session_id,
}
}
}
/// A high level state machine that handles E2EE for Matrix.
#[derive(uniffi::Object)]
pub struct OlmMachine {
@@ -67,14 +113,15 @@ pub struct OlmMachine {
impl Drop for OlmMachine {
fn drop(&mut self) {
// SAFETY: self.inner is never used again, which is the only requirement
// for ManuallyDrop::take to be used safely.
let inner = unsafe { ManuallyDrop::take(&mut self.inner) };
let _guard = self.runtime.enter();
// Dropping the inner OlmMachine must happen within a tokio context
// because deadpool drops sqlite connections in the DB pool on tokio's
// blocking threadpool to avoid blocking async worker threads.
drop(inner);
let _guard = self.runtime.enter();
// SAFETY: self.inner is never used again, which is the only requirement
// for ManuallyDrop::drop to be used safely.
unsafe {
ManuallyDrop::drop(&mut self.inner);
}
}
}
@@ -474,7 +521,8 @@ impl OlmMachine {
device_changes: DeviceLists,
key_counts: HashMap<String, i32>,
unused_fallback_keys: Option<Vec<String>>,
) -> Result<String, CryptoStoreError> {
next_batch_token: String,
) -> Result<SyncChangesResult, CryptoStoreError> {
let to_device: ToDevice = serde_json::from_str(&events)?;
let device_changes: RumaDeviceLists = device_changes.into();
let key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = key_counts
@@ -492,14 +540,21 @@ impl OlmMachine {
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> =
unused_fallback_keys.map(|u| u.into_iter().map(DeviceKeyAlgorithm::from).collect());
let events = self.runtime.block_on(self.inner.receive_sync_changes(
to_device.events,
&device_changes,
&key_counts,
unused_fallback_keys.as_deref(),
))?;
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),
}),
)?;
Ok(serde_json::to_string(&events)?)
let to_device_events =
to_device_events.into_iter().map(|event| event.json().get().to_owned()).collect();
let room_key_infos = room_key_infos.into_iter().map(|info| info.into()).collect();
Ok(SyncChangesResult { to_device_events, room_key_infos })
}
/// Add the given list of users to be tracked, triggering a key query
@@ -512,7 +567,7 @@ impl OlmMachine {
///
/// *Note*: Only users that aren't already tracked will be considered for an
/// update. It's safe to call this with already tracked users, it won't
/// result in excessive keys query requests.
/// result in excessive `/keys/query` requests.
///
/// # Arguments
///
@@ -710,8 +765,8 @@ impl OlmMachine {
/// method. This method call should be locked per call.
///
/// 2. Share a room key with all the room members using the
/// [`share_room_key()`](Self::share_room_key). This method
/// call should be locked per room.
/// [`share_room_key()`](Self::share_room_key). This method call should
/// be locked per room.
///
/// 3. Encrypt the event using this method.
///
@@ -735,11 +790,11 @@ impl OlmMachine {
content: String,
) -> Result<String, CryptoStoreError> {
let room_id = RoomId::parse(room_id)?;
let content: Value = serde_json::from_str(&content)?;
let content = serde_json::from_str(&content)?;
let encrypted_content = self
.runtime
.block_on(self.inner.encrypt_room_event_raw(&room_id, content, &event_type))
.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)?)
@@ -1222,8 +1277,10 @@ impl OlmMachine {
///
/// This method returns `None` if we don't have any private cross signing
/// keys.
pub fn export_cross_signing_keys(&self) -> Option<CrossSigningKeyExport> {
self.runtime.block_on(self.inner.export_cross_signing_keys()).map(|e| e.into())
pub fn export_cross_signing_keys(
&self,
) -> Result<Option<CrossSigningKeyExport>, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.export_cross_signing_keys())?.map(|e| e.into()))
}
/// Import our private cross signing keys.
@@ -1239,6 +1296,16 @@ impl OlmMachine {
Ok(())
}
/// 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.
///
/// The requests will be processed as soon as `outgoing_requests()` is
/// called to process them.
pub fn query_missing_secrets_from_other_sessions(&self) -> Result<bool, CryptoStoreError> {
Ok(self.runtime.block_on(self.inner.query_missing_secrets_from_other_sessions())?)
}
/// Activate the given backup key to be used with the given backup version.
///
/// **Warning**: The caller needs to make sure that the given `BackupKey` is
@@ -1303,12 +1370,12 @@ impl OlmMachine {
// We need to clone here due to FFI limitations but RecoveryKey does
// not want to expose clone since it's private key material.
let mut encoded = k.to_base64();
let key = RecoveryKey::from_base64(&encoded)
let key = BackupDecryptionKey::from_base64(&encoded)
.expect("Encoding and decoding from base64 should always work");
encoded.zeroize();
key
});
Ok(self.runtime.block_on(self.inner.backup_machine().save_recovery_key(key, version))?)
Ok(self.runtime.block_on(self.inner.backup_machine().save_decryption_key(key, version))?)
}
/// Get the backup keys we have saved in our crypto store.
@@ -1323,9 +1390,13 @@ impl OlmMachine {
/// Sign the given message using our device key and if available cross
/// signing master key.
pub fn sign(&self, message: String) -> HashMap<String, HashMap<String, String>> {
self.runtime
.block_on(self.inner.sign(&message))
pub fn sign(
&self,
message: String,
) -> Result<HashMap<String, HashMap<String, String>>, CryptoStoreError> {
Ok(self
.runtime
.block_on(self.inner.sign(&message))?
.into_iter()
.map(|(k, v)| {
(
@@ -1343,7 +1414,7 @@ impl OlmMachine {
.collect(),
)
})
.collect()
.collect())
}
/// Check if the given backup has been verified by us or by another of our
@@ -1372,6 +1443,15 @@ impl OlmMachine {
.block_on(self.inner.backup_machine().verify_backup(backup_info, false))?
.into())
}
/// Manage dehydrated devices.
pub fn dehydrated_devices(&self) -> Arc<DehydratedDevices> {
DehydratedDevices {
inner: ManuallyDrop::new(self.inner.dehydrated_devices()),
runtime: self.runtime.handle().to_owned(),
}
.into()
}
}
impl OlmMachine {
@@ -1385,6 +1465,7 @@ impl OlmMachine {
progress_listener.on_progress(progress as i32, total as i32)
};
#[allow(deprecated)]
let result =
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
@@ -1,59 +1,5 @@
namespace matrix_sdk_crypto_ffi {};
callback interface Logger {
void log(string log_line);
};
callback interface ProgressListener {
void on_progress(i32 progress, i32 total);
};
dictionary CancelInfo {
string cancel_code;
string reason;
boolean cancelled_by_us;
};
[Enum]
interface SasState {
Started();
Accepted();
KeysExchanged(sequence<i32>? emojis, sequence<i32> decimals);
Confirmed();
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface SasListener {
void on_change(SasState state);
};
[Enum]
interface QrCodeState {
Started();
Scanned();
Confirmed();
Reciprocated();
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface QrCodeListener {
void on_change(QrCodeState state);
};
[Enum]
interface VerificationRequestState {
Requested();
Ready(sequence<string> their_methods, sequence<string> our_methods);
Done();
Cancelled(CancelInfo cancel_info);
};
callback interface VerificationRequestListener {
void on_change(VerificationRequestState state);
};
enum LocalTrust {
"Verified",
"BlackListed",
+31 -16
View File
@@ -4,8 +4,9 @@ use std::collections::HashMap;
use http::Response;
use matrix_sdk_crypto::{
IncomingResponse, OutgoingRequest, OutgoingVerificationRequest as SdkVerificationRequest,
RoomMessageRequest, ToDeviceRequest, UploadSigningKeysRequest as RustUploadSigningKeysRequest,
CrossSigningBootstrapRequests, IncomingResponse, KeysBackupRequest, OutgoingRequest,
OutgoingVerificationRequest as SdkVerificationRequest, RoomMessageRequest, ToDeviceRequest,
UploadSigningKeysRequest as RustUploadSigningKeysRequest,
};
use ruma::{
api::client::{
@@ -70,17 +71,23 @@ impl From<RustUploadSigningKeysRequest> for UploadSigningKeysRequest {
#[derive(uniffi::Record)]
pub struct BootstrapCrossSigningResult {
/// Optional request to upload some device keys alongside.
///
/// Must be sent first if present, and marked with `mark_request_as_sent`.
pub upload_keys_request: Option<Request>,
/// Request to upload the signing keys. Must be sent second.
pub upload_signing_keys_request: UploadSigningKeysRequest,
pub signature_request: SignatureUploadRequest,
/// Request to upload the keys signatures, including for the signing keys
/// and optionally for the device keys. Must be sent last.
pub upload_signature_request: SignatureUploadRequest,
}
impl From<(RustUploadSigningKeysRequest, RustSignatureUploadRequest)>
for BootstrapCrossSigningResult
{
fn from(requests: (RustUploadSigningKeysRequest, RustSignatureUploadRequest)) -> Self {
impl From<CrossSigningBootstrapRequests> for BootstrapCrossSigningResult {
fn from(requests: CrossSigningBootstrapRequests) -> Self {
Self {
upload_signing_keys_request: requests.0.into(),
signature_request: requests.1.into(),
upload_signing_keys_request: requests.upload_signing_keys_req.into(),
upload_keys_request: requests.upload_keys_req.map(Request::from),
upload_signature_request: requests.upload_signatures_req.into(),
}
}
}
@@ -142,7 +149,7 @@ impl From<OutgoingRequest> for Request {
Request::KeysUpload {
request_id: r.request_id().to_string(),
body: serde_json::to_string(&body)
.expect("Can't serialize keys upload request"),
.expect("Can't serialize `/keys/upload` request"),
}
}
KeysQuery(k) => {
@@ -157,12 +164,7 @@ impl From<OutgoingRequest> for Request {
},
RoomMessage(r) => Request::from(r),
KeysClaim(c) => (r.request_id().to_owned(), c.clone()).into(),
KeysBackup(b) => Request::KeysBackup {
request_id: r.request_id().to_string(),
version: b.version.to_owned(),
rooms: serde_json::to_string(&b.rooms)
.expect("Can't serialize keys backup request"),
},
KeysBackup(b) => (r.request_id().to_owned(), b.clone()).into(),
}
}
}
@@ -197,6 +199,19 @@ impl From<(OwnedTransactionId, KeysClaimRequest)> for Request {
}
}
impl From<(OwnedTransactionId, KeysBackupRequest)> for Request {
fn from(request_tuple: (OwnedTransactionId, KeysBackupRequest)) -> Self {
let (request_id, request) = request_tuple;
Request::KeysBackup {
request_id: request_id.to_string(),
version: request.version.to_owned(),
rooms: serde_json::to_string(&request.rooms)
.expect("Can't serialize keys backup request"),
}
}
}
impl From<&ToDeviceRequest> for Request {
fn from(r: &ToDeviceRequest) -> Self {
Request::ToDevice {
@@ -1,10 +1,5 @@
use std::sync::Arc;
use base64::{
alphabet,
engine::{general_purpose, GeneralPurpose},
Engine,
};
use futures_util::{Stream, StreamExt};
use matrix_sdk_crypto::{
matrix_sdk_qrcode::QrVerificationData, CancelInfo as RustCancelInfo, QrVerification as InnerQr,
@@ -14,14 +9,13 @@ use matrix_sdk_crypto::{
};
use ruma::events::key::verification::VerificationMethod;
use tokio::runtime::Handle;
use vodozemac::{base64_decode, base64_encode};
use crate::{CryptoStoreError, OutgoingVerificationRequest, SignatureUploadRequest};
const STANDARD_NO_PAD: GeneralPurpose =
GeneralPurpose::new(&alphabet::STANDARD, general_purpose::NO_PAD);
/// Listener that will be passed over the FFI to report changes to a SAS
/// verification.
#[uniffi::export(callback_interface)]
pub trait SasListener: Send {
/// The callback that should be called on the Rust side
///
@@ -32,6 +26,7 @@ pub trait SasListener: Send {
}
/// An Enum describing the state the SAS verification is in.
#[derive(uniffi::Enum)]
pub enum SasState {
/// The verification has been started, the protocols that should be used
/// have been proposed and can be accepted.
@@ -277,6 +272,7 @@ impl Sas {
/// Listener that will be passed over the FFI to report changes to a QrCode
/// verification.
#[uniffi::export(callback_interface)]
pub trait QrCodeListener: Send {
/// The callback that should be called on the Rust side
///
@@ -287,6 +283,7 @@ pub trait QrCodeListener: Send {
}
/// An Enum describing the state the QrCode verification is in.
#[derive(uniffi::Enum)]
pub enum QrCodeState {
/// The QR verification has been started.
Started,
@@ -416,7 +413,7 @@ impl QrCode {
/// decoded on the other side before it can be put through a QR code
/// generator.
pub fn generate_qr_code(&self) -> Option<String> {
self.inner.to_bytes().map(|data| STANDARD_NO_PAD.encode(data)).ok()
self.inner.to_bytes().map(base64_encode).ok()
}
/// Set a listener for changes in the QrCode verification process.
@@ -458,6 +455,7 @@ impl QrCode {
}
/// Information on why a verification flow has been cancelled and by whom.
#[derive(uniffi::Record)]
pub struct CancelInfo {
/// The textual representation of the cancel reason
pub reason: String,
@@ -520,6 +518,7 @@ pub struct ConfirmVerificationResult {
/// Listener that will be passed over the FFI to report changes to a
/// verification request.
#[uniffi::export(callback_interface)]
pub trait VerificationRequestListener: Send {
/// The callback that should be called on the Rust side
///
@@ -530,6 +529,7 @@ pub trait VerificationRequestListener: Send {
}
/// An Enum describing the state the QrCode verification is in.
#[derive(uniffi::Enum)]
pub enum VerificationRequestState {
/// The verification request was sent
Requested,
@@ -706,7 +706,7 @@ impl VerificationRequest {
/// * `data` - The data that was extracted from the scanned QR code as an
/// base64 encoded string, without padding.
pub fn scan_qr_code(&self, data: String) -> Option<ScanResult> {
let data = STANDARD_NO_PAD.decode(data).ok()?;
let data = base64_decode(data).ok()?;
let data = QrVerificationData::from_bytes(data).ok()?;
if let Some(qr) = self.runtime.block_on(self.inner.scan_qr_code(data)).ok()? {
@@ -1,2 +0,0 @@
[build]
target = "wasm32-unknown-unknown"
-3
View File
@@ -1,3 +0,0 @@
/docs
/node_modules
/package-lock.json
@@ -1 +0,0 @@
/pkg
@@ -1,9 +0,0 @@
// prettier configuration: the same as the conventions used throughout Matrix.org
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
module.exports = {
printWidth: 120,
tabWidth: 4,
quoteProps: "consistent",
trailingComma: "all",
};
-2
View File
@@ -1,2 +0,0 @@
version-tag-prefix "matrix-sdk-crypto-js-"
version-git-message "matrix-sdk-crypto-js v%s"
@@ -1,46 +0,0 @@
# v0.1.0-alpha.11
## Changes in the Javascript bindings
- Simplify the response type of `Sas.confirm()`.
- Add `VerificationRequest.registerChangesCallback()`,
`Sas.registerChangesCallback()`, and `Qr.registerChangesCallback()`.
- Add `VerificationRequest.phase()` and `VerificationRequest.getVerification()`.
## Changes in the underlying Rust crate
- Add support for the `hkdf-hmac-sha256.v2` SAS message authentication code.
- Ensure that the correct short authentication strings are used when accepting a
SAS verification with the `Sas::accept()` method.
# v0.1.0-alpha.10
- Add `masterKey`, `userSigningKey`, `selfSigningKey` to `UserIdentity` and `OwnUserIdentity`
# v0.1.0-alpha.9
- Extend `OlmDevice.markRequestAsSent` to accept responses to
`SigningKeysUploadRequest`s.
- Add a missing `const` for compatibility with ECMAScript Module compatibility
mode.
- Fix the body of `SignatureUploadRequest`s to match the spec.
- Add a constructor for `SigningKeysUploadRequest`.
# v0.1.0-alpha.8
- `importCrossSigningKeys`: change the parameters to be individual keys
rather than a `CrossSigningKeyExport` object.
- Make `unused_fallback_keys` optional in `Machine.receive_sync_changes`
# v0.1.0-alpha.7
- Add new accessors `Device.algorithms` and `Device.isSignedByOwner`
- In `OlmMachine.getUserDevices`, wait a limited time for any in-flight
device-list updates to complete.
# v0.1.0-alpha.6
- Add new accessor `InboundGroupSession.senderKey`.
- Add a new API, `OlmMachine.registerRoomKeyUpdatedCallback`, which
applications can use to listen for received room keys.
-59
View File
@@ -1,59 +0,0 @@
[package]
name = "matrix-sdk-crypto-js"
description = "Matrix encryption library, for JavaScript"
authors = ["Ivan Enderlin <ivane@element.io>"]
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = { workspace = true }
version = "0.1.0-alpha.0"
publish = false
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.wasm-pack.profile.profiling]
wasm-opt = false
[package.metadata.wasm-pack.profile.profiling.wasm-bindgen]
debug-js-glue = false
demangle-name-section = true
dwarf-debug-info = true
[package.metadata.wasm-pack.profile.release]
wasm-opt = ['-Oz']
[lib]
crate-type = ["cdylib"]
[features]
default = ["tracing", "qrcode"]
qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
tracing = []
[dependencies]
anyhow = { workspace = true }
console_error_panic_hook = "0.1.7"
futures-util = "0.3.27"
http = { workspace = true }
js-sys = "0.3.49"
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb" }
matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
ruma = { workspace = true, features = ["js", "rand"] }
serde_json = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
vodozemac = { workspace = true, features = ["js"] }
wasm-bindgen = "0.2.83"
wasm-bindgen-futures = "0.4.33"
zeroize = { workspace = true }
[dependencies.matrix-sdk-crypto]
path = "../../crates/matrix-sdk-crypto"
version = "0.6.0"
default_features = false
features = ["js", "automatic-room-key-forwarding"]
-57
View File
@@ -1,57 +0,0 @@
# `matrix-sdk-crypto-js`
Welcome to the [WebAssembly] + JavaScript binding for the Rust
[`matrix-sdk-crypto`] library! WebAssembly can run anywhere, but these
bindings are designed to run on a JavaScript host. These bindings are
part of the [`matrix-rust-sdk`] project, which is a library
implementation of a [Matrix] client-server.
`matrix-sdk-crypto` is a no-network-IO implementation of a state
machine, named `OlmMachine`, that handles E2EE ([End-to-End
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
[Matrix] clients.
## Usage
These WebAssembly bindings are written in [Rust]. To build them, you
need to install the Rust compiler, see [the Install Rust
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
pretty classical by using [yarn](https://yarnpkg.com/), see [the Downloading and installing
Node.js and npm
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) and [installing yarn](https://classic.yarnpkg.com/lang/en/docs/install).
Once the Rust compiler, Node.js and yarn are installed, you can run the
following commands:
```sh
$ yarn install
$ yarn build
$ yarn test
```
A `matrix_sdk_crypto.js`, `matrix_sdk_crypto.d.ts` and a
`matrix_sdk_crypto_bg.wasm` files should be generated in the `pkg/`
directory.
TBD
## Documentation
[The documentation can be found
online](https://matrix-org.github.io/matrix-rust-sdk/bindings/matrix-sdk-crypto-js/).
To generate the documentation locally, please run the following
command:
```sh
$ yarn doc
```
The documentation is generated in the `./docs` directory.
[WebAssembly]: https://webassembly.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
[npm]: https://www.npmjs.com/
@@ -1,13 +0,0 @@
# Steps for releasing `matrix-sdk-crypto-js`
1. Create a new branch, named `release-matrix-sdk-crypto-js-<version>`.
2. Update `CHANGELOG.md`, if necessary.
3. Run `yarn version` to bump the version number and create a tag.
4. Push the branch, but not yet the tag.
5. Create a PR to approve the changes.
6. Once approved:
1. Update the git tag to the new head of the branch, if necessary.
2. Push the git tag; doing so triggers the github actions workflow which
builds and publishes to npm, and creates a draft GH release.
3. Merge the PR.
7. Update the release on github and publish.
@@ -1,50 +0,0 @@
{
"name": "@matrix-org/matrix-sdk-crypto-js",
"version": "0.1.0-alpha.11",
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
"description": "Matrix encryption library, for JavaScript",
"license": "Apache-2.0",
"collaborators": [
"Ivan Enderlin <ivane@element.io>"
],
"repository": {
"type": "git",
"url": "https://github.com/matrix-org/matrix-rust-sdk"
},
"keywords": [
"matrix",
"chat",
"messaging",
"ruma",
"nio"
],
"main": "pkg/matrix_sdk_crypto_js.js",
"types": "pkg/matrix_sdk_crypto_js.d.ts",
"files": [
"pkg/matrix_sdk_crypto_js_bg.wasm.js",
"pkg/matrix_sdk_crypto_js_bg.wasm.d.ts",
"pkg/matrix_sdk_crypto_js.js",
"pkg/matrix_sdk_crypto_js.d.ts"
],
"devDependencies": {
"cross-env": "^7.0.3",
"fake-indexeddb": "^4.0",
"jest": "^28.1.0",
"prettier": "^2.8.3",
"typedoc": "^0.22.17",
"typescript": "4.7",
"wasm-pack": "^0.10.2",
"yargs-parser": "~21.0.1"
},
"engines": {
"node": ">= 10"
},
"scripts": {
"lint": "prettier --check .",
"build": "WASM_PACK_ARGS=--release ./scripts/build.sh",
"build:dev": "WASM_PACK_ARGS=--dev ./scripts/build.sh",
"test": "jest --verbose",
"doc": "typedoc --tsconfig .",
"prepack": "npm run build && npm run test"
}
}
@@ -1,39 +0,0 @@
#!/bin/bash
#
# Build the JavaScript modules
#
# This script is really a workaround for https://github.com/rustwasm/wasm-pack/issues/1074.
#
# Currently, the only reliable way to load WebAssembly in all the JS
# environments we want to target (web-via-webpack, web-via-browserify, jest)
# seems to be to pack the WASM into base64, and then unpack it and instantiate
# it at runtime.
#
# Hopefully one day, https://github.com/rustwasm/wasm-pack/issues/1074 will be
# fixed and this will be unnecessary.
set -e
cd $(dirname "$0")/..
RUSTFLAGS='-C opt-level=z' WASM_BINDGEN_WEAKREF=1 wasm-pack build --target nodejs --scope matrix-org --out-dir pkg "${WASM_PACK_ARGS[@]}"
# Convert the Wasm into a JS file that exports the base64'ed Wasm.
{
printf 'module.exports = `'
base64 < pkg/matrix_sdk_crypto_js_bg.wasm
printf '`;'
} > pkg/matrix_sdk_crypto_js_bg.wasm.js
# In the JavaScript:
# 1. Strip out the lines that load the WASM, and our new epilogue.
# 2. Remove the imports of `TextDecoder` and `TextEncoder`. We rely on the global defaults.
{
sed -e '/Text..coder.*= require(.util.)/d' \
-e '/^const path = /,$d' pkg/matrix_sdk_crypto_js.js
cat scripts/epilogue.js
} > pkg/matrix_sdk_crypto_js.js.new
mv pkg/matrix_sdk_crypto_js.js.new pkg/matrix_sdk_crypto_js.js
# also extend the typescript
cat scripts/epilogue.d.ts >> pkg/matrix_sdk_crypto_js.d.ts
-8
View File
@@ -1,8 +0,0 @@
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
export function initAsync(): Promise<void>;
@@ -1,88 +0,0 @@
// Initially, 'wasm' is set to a Proxy object which will synchronously load the WebAssembly module and
// replace 'wasm' with a reference to the exports from the wasm module.
//
// Ideally this will never get used because the application will call initAsync instead.
wasm = new Proxy(
{},
{
get: (target, prop, receiver) => __initSync()[prop],
},
);
let inited = false;
const __initSync = function () {
if (inited) {
return;
}
if (initPromise) {
throw new Error("Asynchronous initialisation already in progress: cannot initialise synchronously");
}
const bytes = unbase64(require("./matrix_sdk_crypto_js_bg.wasm.js"));
const mod = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(mod, imports);
wasm = instance.exports;
wasm.__wbindgen_start();
inited = true;
return wasm;
};
let initPromise = null;
/**
* Load the WebAssembly module in the background, if it has not already been loaded.
*
* Returns a promise which will resolve once the other methods are ready.
*
* @returns {Promise<void>}
*/
module.exports.initAsync = function () {
if (inited) {
return Promise.resolve();
}
if (!initPromise) {
initPromise = Promise.resolve()
.then(() => require("./matrix_sdk_crypto_js_bg.wasm.js"))
.then((b64) => WebAssembly.instantiate(unbase64(b64), imports))
.then((result) => {
wasm = result.instance.exports;
wasm.__wbindgen_start();
inited = true;
});
}
return initPromise;
};
const b64lookup = new Uint8Array([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 62, 0, 62, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
]);
// base64 decoder, based on the code at https://developer.mozilla.org/en-US/docs/Glossary/Base64#solution_2_%E2%80%93_rewriting_atob_and_btoa_using_typedarrays_and_utf-8
function unbase64(sBase64) {
const sB64Enc = sBase64.replace(/[^A-Za-z0-9+/]/g, "");
const nInLen = sB64Enc.length;
const nOutLen = (nInLen * 3 + 1) >> 2;
const taBytes = new Uint8Array(nOutLen);
let nMod3;
let nMod4;
let nUint24 = 0;
let nOutIdx = 0;
for (let nInIdx = 0; nInIdx < nInLen; nInIdx++) {
nMod4 = nInIdx & 3;
nUint24 |= b64lookup[sB64Enc.charCodeAt(nInIdx)] << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
nMod3 = 0;
while (nMod3 < 3 && nOutIdx < nOutLen) {
taBytes[nOutIdx] = (nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
nMod3++;
nOutIdx++;
}
nUint24 = 0;
}
}
return taBytes;
}
@@ -1,121 +0,0 @@
//! Attachment API.
use std::io::{Cursor, Read};
use wasm_bindgen::prelude::*;
/// A type to encrypt and to decrypt anything that can fit in an
/// `Uint8Array`, usually big buffer.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Attachment;
#[wasm_bindgen]
impl Attachment {
/// Encrypt the content of the `Uint8Array`.
///
/// It produces an `EncryptedAttachment`, which can be used to
/// retrieve the media encryption information, or the encrypted
/// data.
#[wasm_bindgen]
pub fn encrypt(array: &[u8]) -> Result<EncryptedAttachment, JsError> {
let mut cursor = Cursor::new(array);
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
let mut encrypted_data = Vec::new();
encryptor.read_to_end(&mut encrypted_data)?;
let media_encryption_info = Some(encryptor.finish());
Ok(EncryptedAttachment { encrypted_data, media_encryption_info })
}
/// Decrypt an `EncryptedAttachment`.
///
/// The encrypted attachment can be created manually, or from the
/// `encrypt` method.
///
/// **Warning**: The encrypted attachment can be used only
/// **once**! The encrypted data will still be present, but the
/// media encryption info (which contain secrets) will be
/// destroyed. It is still possible to get a JSON-encoded backup
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
pub fn decrypt(attachment: &mut EncryptedAttachment) -> Result<Vec<u8>, JsError> {
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
return Err(JsError::new(
"The media encryption info are absent from the given encrypted attachment",
));
};
let encrypted_data: &[u8] = attachment.encrypted_data.as_slice();
let mut cursor = Cursor::new(encrypted_data);
let mut decryptor =
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)?;
let mut decrypted_data = Vec::new();
decryptor.read_to_end(&mut decrypted_data)?;
Ok(decrypted_data)
}
}
/// An encrypted attachment, usually created from `Attachment.encrypt`.
#[wasm_bindgen]
#[derive(Debug)]
pub struct EncryptedAttachment {
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
encrypted_data: Vec<u8>,
}
#[wasm_bindgen]
impl EncryptedAttachment {
/// Create a new encrypted attachment manually.
///
/// It needs encrypted data, stored in an `Uint8Array`, and a
/// [media encryption
/// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html),
/// as a JSON-encoded string.
///
/// The media encryption information aren't stored as a string:
/// they are parsed, validated and fully deserialized.
///
/// See [the specification to learn
/// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes).
#[wasm_bindgen(constructor)]
pub fn new(
encrypted_data: Vec<u8>,
media_encryption_info: &str,
) -> Result<EncryptedAttachment, JsError> {
Ok(Self {
encrypted_data,
media_encryption_info: Some(serde_json::from_str(media_encryption_info)?),
})
}
/// The actual encrypted data.
///
/// **Warning**: It returns a **copy** of the entire encrypted
/// data; be nice with your memory.
#[wasm_bindgen(getter, js_name = "encryptedData")]
pub fn encrypted_data(&self) -> Vec<u8> {
self.encrypted_data.clone()
}
/// Return the media encryption info as a JSON-encoded string. The
/// structure is fully valid.
///
/// If the media encryption info have been consumed already, it
/// will return `null`.
#[wasm_bindgen(getter, js_name = "mediaEncryptionInfo")]
pub fn media_encryption_info(&self) -> Option<String> {
serde_json::to_string(self.media_encryption_info.as_ref()?).ok()
}
/// Check whether the media encryption info has been consumed by
/// `Attachment.decrypt` already.
#[wasm_bindgen(getter, js_name = "hasMediaEncryptionInfoBeenConsumed")]
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
self.media_encryption_info.is_none()
}
}
-279
View File
@@ -1,279 +0,0 @@
//! Types for a `Device`.
use js_sys::{Array, Map, Promise};
use wasm_bindgen::prelude::*;
use crate::{
encryption::EncryptionAlgorithm,
future::future_to_promise,
identifiers::{self, DeviceId, UserId},
impl_from_to_inner,
js::try_array_to_vec,
types, verification, vodozemac,
};
/// A device represents a E2EE capable client of an user.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Device {
pub(crate) inner: matrix_sdk_crypto::Device,
}
impl_from_to_inner!(matrix_sdk_crypto::Device => Device);
#[wasm_bindgen]
impl Device {
/// Request an interactive verification with this device.
///
/// Returns a Promise for a 2-element array `[VerificationRequest,
/// ToDeviceRequest]`.
#[wasm_bindgen(js_name = "requestVerification")]
pub fn request_verification(&self, methods: Option<Array>) -> Result<Promise, JsError> {
let methods =
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
let tuple = Array::new();
let (verification_request, outgoing_verification_request) = match methods {
Some(methods) => me.request_verification_with_methods(methods).await,
None => me.request_verification().await,
};
tuple.set(0, verification::VerificationRequest::from(verification_request).into());
tuple.set(
1,
verification::OutgoingVerificationRequest::from(outgoing_verification_request)
.try_into()?,
);
Ok(tuple)
}))
}
/// Is this device considered to be verified.
///
/// This method returns true if either the `is_locally_trusted`
/// method returns `true` or if the `is_cross_signing_trusted`
/// method returns `true`.
#[wasm_bindgen(js_name = "isVerified")]
pub fn is_verified(&self) -> bool {
self.inner.is_verified()
}
/// Is this device considered to be verified using cross signing.
#[wasm_bindgen(js_name = "isCrossSigningTrusted")]
pub fn is_cross_signing_trusted(&self) -> bool {
self.inner.is_cross_signing_trusted()
}
/// Is this device cross-signed by its owner?
#[wasm_bindgen(js_name = "isCrossSignedByOwner")]
pub fn is_cross_signed_by_owner(&self) -> bool {
self.inner.is_cross_signed_by_owner()
}
/// Set the local trust state of the device to the given state.
///
/// This wont affect any cross signing trust state, this only
/// sets a flag marking to have the given trust state.
///
/// `trust_state` represents the new trust state that should be
/// set for the device.
#[wasm_bindgen(js_name = "setLocalTrust")]
pub fn set_local_trust(&self, local_state: LocalTrust) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
me.set_local_trust(local_state.into()).await?;
Ok(JsValue::NULL)
})
}
/// The user ID of the device owner.
#[wasm_bindgen(getter, js_name = "userId")]
pub fn user_id(&self) -> UserId {
self.inner.user_id().to_owned().into()
}
/// The unique ID of the device.
#[wasm_bindgen(getter, js_name = "deviceId")]
pub fn device_id(&self) -> DeviceId {
self.inner.device_id().to_owned().into()
}
/// Get the human readable name of the device.
#[wasm_bindgen(getter, js_name = "displayName")]
pub fn display_name(&self) -> Option<String> {
self.inner.display_name().map(ToOwned::to_owned)
}
/// Get the key of the given key algorithm belonging to this device.
#[wasm_bindgen(js_name = "getKey")]
pub fn get_key(
&self,
algorithm: identifiers::DeviceKeyAlgorithmName,
) -> Result<Option<vodozemac::DeviceKey>, JsError> {
Ok(self.inner.get_key(algorithm.try_into()?).cloned().map(Into::into))
}
/// Get the Curve25519 key of the given device.
#[wasm_bindgen(getter, js_name = "curve25519Key")]
pub fn curve25519_key(&self) -> Option<vodozemac::Curve25519PublicKey> {
self.inner.curve25519_key().map(Into::into)
}
/// Get the Ed25519 key of the given device.
#[wasm_bindgen(getter, js_name = "ed25519Key")]
pub fn ed25519_key(&self) -> Option<vodozemac::Ed25519PublicKey> {
self.inner.ed25519_key().map(Into::into)
}
/// Get a map containing all the device keys.
#[wasm_bindgen(getter)]
pub fn keys(&self) -> Map {
let map = Map::new();
for (device_key_id, device_key) in self.inner.keys() {
map.set(
&identifiers::DeviceKeyId::from(device_key_id.clone()).into(),
&vodozemac::DeviceKey::from(device_key.clone()).into(),
);
}
map
}
/// Get the list of algorithms this device supports.
///
/// Returns `Array<EncryptionAlgorithm>`.
#[wasm_bindgen(getter)]
pub fn algorithms(&self) -> Array {
self.inner
.algorithms()
.iter()
.map(|alg| EncryptionAlgorithm::from(alg.clone()))
.map(JsValue::from)
.collect()
}
/// Get a map containing all the device signatures.
#[wasm_bindgen(getter)]
pub fn signatures(&self) -> types::Signatures {
self.inner.signatures().clone().into()
}
/// Get the trust state of the device.
#[wasm_bindgen(getter, js_name = "localTrustState")]
pub fn local_trust_state(&self) -> LocalTrust {
self.inner.local_trust_state().into()
}
/// Is the device locally marked as trusted?
#[wasm_bindgen(js_name = "isLocallyTrusted")]
pub fn is_locally_trusted(&self) -> bool {
self.inner.is_locally_trusted()
}
/// Is the device locally marked as blacklisted?
///
/// Blacklisted devices wont receive any group sessions.
#[wasm_bindgen(js_name = "isBlacklisted")]
pub fn is_blacklisted(&self) -> bool {
self.inner.is_blacklisted()
}
/// Is the device deleted?
#[wasm_bindgen(js_name = "isDeleted")]
pub fn is_deleted(&self) -> bool {
self.inner.is_deleted()
}
/// Timestamp representing the first time this device has been seen (in
/// milliseconds).
#[wasm_bindgen(js_name = "firstTimeSeen")]
pub fn first_time_seen(&self) -> u64 {
self.inner.first_time_seen_ts().0.into()
}
}
/// The local trust state of a device.
#[wasm_bindgen]
#[derive(Debug)]
pub enum LocalTrust {
/// The device has been verified and is trusted.
Verified,
/// The device been blacklisted from communicating.
BlackListed,
/// The trust state of the device is being ignored.
Ignored,
/// The trust state is unset.
Unset,
}
impl From<matrix_sdk_crypto::LocalTrust> for LocalTrust {
fn from(value: matrix_sdk_crypto::LocalTrust) -> Self {
use matrix_sdk_crypto::LocalTrust::*;
match value {
Verified => Self::Verified,
BlackListed => Self::BlackListed,
Ignored => Self::Ignored,
Unset => Self::Unset,
}
}
}
impl From<LocalTrust> for matrix_sdk_crypto::LocalTrust {
fn from(value: LocalTrust) -> Self {
use LocalTrust::*;
match value {
Verified => Self::Verified,
BlackListed => Self::BlackListed,
Ignored => Self::Ignored,
Unset => Self::Unset,
}
}
}
/// A read only view over all devices belonging to a user.
#[wasm_bindgen]
#[derive(Debug)]
pub struct UserDevices {
pub(crate) inner: matrix_sdk_crypto::UserDevices,
}
impl_from_to_inner!(matrix_sdk_crypto::UserDevices => UserDevices);
#[wasm_bindgen]
impl UserDevices {
/// Get the specific device with the given device ID.
pub fn get(&self, device_id: &DeviceId) -> Option<Device> {
self.inner.get(&device_id.inner).map(Into::into)
}
/// Returns true if there is at least one devices of this user
/// that is considered to be verified, false otherwise.
///
/// This won't consider your own device as verified, as your own
/// device is always implicitly verified.
#[wasm_bindgen(js_name = "isAnyVerified")]
pub fn is_any_verified(&self) -> bool {
self.inner.is_any_verified()
}
/// Array over all the device IDs of the user devices.
pub fn keys(&self) -> Array {
self.inner.keys().map(ToOwned::to_owned).map(DeviceId::from).map(JsValue::from).collect()
}
/// Iterator over all the devices of the user devices.
pub fn devices(&self) -> Array {
self.inner.devices().map(Device::from).map(JsValue::from).collect()
}
}
@@ -1,167 +0,0 @@
//! Encryption types & siblings.
use std::time::Duration;
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use wasm_bindgen::prelude::*;
use crate::events;
/// Settings for an encrypted room.
///
/// This determines the algorithm and rotation periods of a group
/// session.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug, Clone)]
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EncryptionAlgorithm,
/// How long the session should be used before changing it,
/// expressed in microseconds.
#[wasm_bindgen(js_name = "rotationPeriod")]
pub rotation_period: u64,
/// How many messages should be sent before changing the session.
#[wasm_bindgen(js_name = "rotationPeriodMessages")]
pub rotation_period_messages: u64,
/// The history visibility of the room when the session was
/// created.
#[wasm_bindgen(js_name = "historyVisibility")]
pub history_visibility: events::HistoryVisibility,
/// Should untrusted devices receive the room key, or should they be
/// excluded from the conversation.
#[wasm_bindgen(js_name = "onlyAllowTrustedDevices")]
pub only_allow_trusted_devices: bool,
}
impl Default for EncryptionSettings {
fn default() -> Self {
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
Self {
algorithm: default.algorithm.into(),
rotation_period: default.rotation_period.as_micros().try_into().unwrap(),
rotation_period_messages: default.rotation_period_msgs,
history_visibility: default.history_visibility.into(),
only_allow_trusted_devices: default.only_allow_trusted_devices,
}
}
}
#[wasm_bindgen]
impl EncryptionSettings {
/// Create a new `EncryptionSettings` with default values.
#[wasm_bindgen(constructor)]
pub fn new() -> EncryptionSettings {
Self::default()
}
}
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
fn from(value: &EncryptionSettings) -> Self {
let algorithm = value.algorithm.clone().into();
Self {
algorithm,
rotation_period: Duration::from_micros(value.rotation_period),
rotation_period_msgs: value.rotation_period_messages,
history_visibility: value.history_visibility.clone().into(),
only_allow_trusted_devices: value.only_allow_trusted_devices,
}
}
}
/// An encryption algorithm to be used to encrypt messages sent to a
/// room.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub enum EncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for JsValue {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => JsValue::from(0),
MegolmV1AesSha2 => JsValue::from(1),
}
}
}
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
}
}
}
impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: matrix_sdk_crypto::types::EventEncryptionAlgorithm) -> Self {
use matrix_sdk_crypto::types::EventEncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
_ => unreachable!("Unknown variant"),
}
}
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[wasm_bindgen]
#[derive(Debug, Clone, Copy)]
pub enum ShieldColor {
/// Important warning
Red,
/// Low warning
Grey,
/// No warning
None,
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct ShieldState {
/// The shield color
pub color: ShieldColor,
message: Option<String>,
}
#[wasm_bindgen]
impl ShieldState {
/// Error message that can be displayed as a tooltip
#[wasm_bindgen(getter)]
pub fn message(&self) -> Option<String> {
self.message.clone()
}
}
impl From<RustShieldState> for ShieldState {
fn from(value: RustShieldState) -> Self {
match value {
RustShieldState::Red { message } => {
Self { color: ShieldColor::Red, message: Some(message.to_owned()) }
}
RustShieldState::Grey { message } => {
Self { color: ShieldColor::Grey, message: Some(message.to_owned()) }
}
RustShieldState::None => Self { color: ShieldColor::None, message: None },
}
}
}
@@ -1,61 +0,0 @@
//! Types related to events.
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
use wasm_bindgen::prelude::*;
/// Who can see a room's history.
#[wasm_bindgen]
#[derive(Debug, Clone)]
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's 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's 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,
}
impl From<HistoryVisibility> for RumaHistoryVisibility {
fn from(value: HistoryVisibility) -> Self {
use HistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
}
}
}
impl From<RumaHistoryVisibility> for HistoryVisibility {
fn from(value: RumaHistoryVisibility) -> Self {
use RumaHistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
_ => unreachable!("Unknown variant"),
}
}
}
@@ -1,26 +0,0 @@
use std::future::Future;
use js_sys::Promise;
use wasm_bindgen::{JsError, JsValue, UnwrapThrowExt};
use wasm_bindgen_futures::spawn_local;
pub(crate) fn future_to_promise<F, T>(future: F) -> Promise
where
F: Future<Output = Result<T, anyhow::Error>> + 'static,
T: Into<JsValue>,
{
let mut future = Some(future);
Promise::new(&mut |resolve, reject| {
let future = future.take().unwrap_throw();
spawn_local(async move {
match future.await {
Ok(value) => resolve.call1(&JsValue::UNDEFINED, &value.into()).unwrap_throw(),
Err(value) => reject
.call1(&JsValue::UNDEFINED, &JsError::new(&value.to_string()).into())
.unwrap_throw(),
};
});
})
}
@@ -1,321 +0,0 @@
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
//! events, keys, rooms, servers, users and URIs.
use wasm_bindgen::prelude::*;
use crate::impl_from_to_inner;
/// A Matrix [user ID].
///
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct UserId {
pub(crate) inner: ruma::OwnedUserId,
}
impl_from_to_inner!(ruma::OwnedUserId => UserId);
#[wasm_bindgen]
impl UserId {
/// Parse/validate and create a new `UserId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> Result<UserId, JsError> {
Ok(Self::from(ruma::UserId::parse(id)?))
}
/// Returns the user's localpart.
#[wasm_bindgen(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the user ID.
#[wasm_bindgen(getter, js_name = "serverName")]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Whether this user ID is a historical one.
///
/// A historical user ID is one that doesn't conform to the latest
/// specification of the user ID grammar but is still accepted
/// because it was previously allowed.
#[wasm_bindgen(js_name = "isHistorical")]
pub fn is_historical(&self) -> bool {
self.inner.is_historical()
}
/// Return the user ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix key ID.
///
/// Device identifiers in Matrix are completely opaque character
/// sequences. This type is provided simply for its semantic value.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct DeviceId {
pub(crate) inner: ruma::OwnedDeviceId,
}
impl_from_to_inner!(ruma::OwnedDeviceId => DeviceId);
#[wasm_bindgen]
impl DeviceId {
/// Create a new `DeviceId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> DeviceId {
Self::from(ruma::OwnedDeviceId::from(id))
}
/// Return the device ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix device key ID.
///
/// A key algorithm and a device ID, combined with a :.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct DeviceKeyId {
pub(crate) inner: ruma::OwnedDeviceKeyId,
}
impl_from_to_inner!(ruma::OwnedDeviceKeyId => DeviceKeyId);
#[wasm_bindgen]
impl DeviceKeyId {
/// Parse/validate and create a new `DeviceKeyId`.
#[wasm_bindgen(constructor)]
pub fn new(id: String) -> Result<DeviceKeyId, JsError> {
Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str())?))
}
/// Returns key algorithm of the device key ID.
#[wasm_bindgen(getter)]
pub fn algorithm(&self) -> DeviceKeyAlgorithm {
self.inner.algorithm().into()
}
/// Returns device ID of the device key ID.
#[wasm_bindgen(getter, js_name = "deviceId")]
pub fn device_id(&self) -> DeviceId {
self.inner.device_id().to_owned().into()
}
/// Return the device key ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithms in the specification.
#[wasm_bindgen]
#[derive(Debug)]
pub struct DeviceKeyAlgorithm {
pub(crate) inner: ruma::DeviceKeyAlgorithm,
}
impl_from_to_inner!(ruma::DeviceKeyAlgorithm => DeviceKeyAlgorithm);
#[wasm_bindgen]
impl DeviceKeyAlgorithm {
/// Read the device key algorithm's name. If the name is
/// `Unknown`, one may be interested by the `to_string` method to
/// read the original name.
#[wasm_bindgen(getter)]
pub fn name(&self) -> DeviceKeyAlgorithmName {
self.inner.clone().into()
}
/// Return the device key algorithm as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithm names in the specification.
#[wasm_bindgen]
#[derive(Debug)]
pub enum DeviceKeyAlgorithmName {
/// The Ed25519 signature algorithm.
Ed25519,
/// The Curve25519 ECDH algorithm.
Curve25519,
/// The Curve25519 ECDH algorithm, but the key also contains
/// signatures.
SignedCurve25519,
/// An unknown device key algorithm.
Unknown,
}
impl TryFrom<DeviceKeyAlgorithmName> for ruma::DeviceKeyAlgorithm {
type Error = JsError;
fn try_from(value: DeviceKeyAlgorithmName) -> Result<Self, Self::Error> {
use DeviceKeyAlgorithmName::*;
Ok(match value {
Ed25519 => Self::Ed25519,
Curve25519 => Self::Curve25519,
SignedCurve25519 => Self::SignedCurve25519,
Unknown => {
return Err(JsError::new(
"The `DeviceKeyAlgorithmName.Unknown` variant cannot be converted",
))
}
})
}
}
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithmName {
fn from(value: ruma::DeviceKeyAlgorithm) -> Self {
use ruma::DeviceKeyAlgorithm::*;
match value {
Ed25519 => Self::Ed25519,
Curve25519 => Self::Curve25519,
SignedCurve25519 => Self::SignedCurve25519,
_ => Self::Unknown,
}
}
}
/// A Matrix [room ID].
///
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct RoomId {
pub(crate) inner: ruma::OwnedRoomId,
}
impl_from_to_inner!(ruma::OwnedRoomId => RoomId);
#[wasm_bindgen]
impl RoomId {
/// Parse/validate and create a new `RoomId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> Result<RoomId, JsError> {
Ok(Self::from(ruma::RoomId::parse(id)?))
}
/// Returns the user's localpart.
#[wasm_bindgen(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the room ID.
#[wasm_bindgen(getter, js_name = "serverName")]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Return the room ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix-spec compliant [server name].
///
/// It consists of a host and an optional port (separated by a colon if
/// present).
///
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
#[wasm_bindgen]
#[derive(Debug)]
pub struct ServerName {
inner: ruma::OwnedServerName,
}
#[wasm_bindgen]
impl ServerName {
/// Parse/validate and create a new `ServerName`.
#[wasm_bindgen(constructor)]
pub fn new(name: &str) -> Result<ServerName, JsError> {
Ok(Self { inner: ruma::ServerName::parse(name)? })
}
/// Returns the host of the server name.
///
/// That is: Return the part of the server before `:<port>` or the
/// full server name if there is no port.
#[wasm_bindgen(getter)]
pub fn host(&self) -> String {
self.inner.host().to_owned()
}
/// Returns the port of the server name if any.
#[wasm_bindgen(getter)]
pub fn port(&self) -> Option<u16> {
self.inner.port()
}
/// Returns true if and only if the server name is an IPv4 or IPv6
/// address.
#[wasm_bindgen(js_name = "isIpLiteral")]
pub fn is_ip_literal(&self) -> bool {
self.inner.is_ip_literal()
}
}
/// A Matrix [event ID].
///
/// An `EventId` is generated randomly or converted from a string
/// slice, and can be converted back into a string as needed.
///
/// [event ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
#[wasm_bindgen]
#[derive(Debug)]
pub struct EventId {
pub(crate) inner: ruma::OwnedEventId,
}
#[wasm_bindgen]
impl EventId {
/// Parse/validate and create a new `EventId`.
#[wasm_bindgen(constructor)]
pub fn new(id: &str) -> Result<EventId, JsError> {
Ok(Self { inner: <&ruma::EventId>::try_from(id)?.to_owned() })
}
/// Returns the event's localpart.
#[wasm_bindgen(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the event ID.
#[wasm_bindgen(getter, js_name = "serverName")]
pub fn server_name(&self) -> Option<ServerName> {
Some(ServerName { inner: self.inner.server_name()?.to_owned() })
}
/// Return the event ID as a string.
#[wasm_bindgen(js_name = "toString")]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
@@ -1,208 +0,0 @@
//! User identities.
use js_sys::{Array, Promise};
use wasm_bindgen::prelude::*;
use crate::{
future::future_to_promise, identifiers, impl_from_to_inner, js::try_array_to_vec, requests,
verification,
};
pub(crate) struct UserIdentities {
inner: matrix_sdk_crypto::UserIdentities,
}
impl_from_to_inner!(matrix_sdk_crypto::UserIdentities => UserIdentities);
impl From<UserIdentities> for JsValue {
fn from(user_identities: UserIdentities) -> Self {
use matrix_sdk_crypto::UserIdentities::*;
match user_identities.inner {
Own(own) => JsValue::from(OwnUserIdentity::from(own)),
Other(other) => JsValue::from(UserIdentity::from(other)),
}
}
}
/// Struct representing a cross signing identity of a user.
///
/// This is the user identity of a user that is our own.
#[wasm_bindgen]
#[derive(Debug)]
pub struct OwnUserIdentity {
inner: matrix_sdk_crypto::OwnUserIdentity,
}
impl_from_to_inner!(matrix_sdk_crypto::OwnUserIdentity => OwnUserIdentity);
#[wasm_bindgen]
impl OwnUserIdentity {
/// Mark our user identity as verified.
///
/// This will mark the identity locally as verified and sign it with our own
/// device.
///
/// Returns a signature upload request that needs to be sent out.
pub fn verify(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(requests::SignatureUploadRequest::try_from(&me.verify().await?)?)
})
}
/// Send a verification request to our other devices.
#[wasm_bindgen(js_name = "requestVerification")]
pub fn request_verification(&self, methods: Option<Array>) -> Result<Promise, JsError> {
let methods =
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
let tuple = Array::new();
let (verification_request, outgoing_verification_request) = match methods {
Some(methods) => me.request_verification_with_methods(methods).await?,
None => me.request_verification().await?,
};
tuple.set(0, verification::VerificationRequest::from(verification_request).into());
tuple.set(
1,
verification::OutgoingVerificationRequest::from(outgoing_verification_request)
.try_into()?,
);
Ok(tuple)
}))
}
/// Does our user identity trust our own device, i.e. have we signed our own
/// device keys with our self-signing key?
#[wasm_bindgen(js_name = "trustsOurOwnDevice")]
pub fn trusts_our_own_device(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move { Ok(me.trusts_our_own_device().await?) })
}
/// Get the master key of the identity.
#[wasm_bindgen(getter, js_name = "masterKey")]
pub fn master_key(&self) -> Result<String, JsError> {
let master_key = self.inner.master_key().as_ref();
Ok(serde_json::to_string(master_key)?)
}
/// Get the self-signing key of the identity.
#[wasm_bindgen(getter, js_name = "selfSigningKey")]
pub fn self_signing_key(&self) -> Result<String, JsError> {
let self_signing_key = self.inner.self_signing_key().as_ref();
Ok(serde_json::to_string(self_signing_key)?)
}
/// Get the user-signing key of the identity, this is only present for our
/// own user identity..
#[wasm_bindgen(getter, js_name = "userSigningKey")]
pub fn user_signing_key(&self) -> Result<String, JsError> {
let user_signing_key = self.inner.user_signing_key().as_ref();
Ok(serde_json::to_string(user_signing_key)?)
}
}
/// Struct representing a cross signing identity of a user.
///
/// This is the user identity of a user that isn't our own. Other users will
/// only contain a master key and a self signing key, meaning that only device
/// signatures can be checked with this identity.
///
/// This struct wraps a read-only version of the struct and allows verifications
/// to be requested to verify our own device with the user identity.
#[wasm_bindgen]
#[derive(Debug)]
pub struct UserIdentity {
inner: matrix_sdk_crypto::UserIdentity,
}
impl_from_to_inner!(matrix_sdk_crypto::UserIdentity => UserIdentity);
#[wasm_bindgen]
impl UserIdentity {
/// Is this user identity verified?
#[wasm_bindgen(js_name = "isVerified")]
pub fn is_verified(&self) -> bool {
self.inner.is_verified()
}
/// Manually verify this user.
///
/// This method will attempt to sign the user identity using our private
/// cross signing key.
///
/// This method fails if we don't have the private part of our user-signing
/// key.
///
/// Returns a request that needs to be sent out for the user to be marked as
/// verified.
pub fn verify(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(requests::SignatureUploadRequest::try_from(&me.verify().await?)?)
})
}
/// Create a `VerificationRequest` object after the verification
/// request content has been sent out.
#[wasm_bindgen(js_name = "requestVerification")]
pub fn request_verification(
&self,
room_id: &identifiers::RoomId,
request_event_id: &identifiers::EventId,
methods: Option<Array>,
) -> Result<Promise, JsError> {
let me = self.inner.clone();
let room_id = room_id.inner.clone();
let request_event_id = request_event_id.inner.clone();
let methods =
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
Ok(future_to_promise::<_, verification::VerificationRequest>(async move {
Ok(me
.request_verification(room_id.as_ref(), request_event_id.as_ref(), methods)
.await
.into())
}))
}
/// Send a verification request to the given user.
///
/// The returned content needs to be sent out into a DM room with the given
/// user.
///
/// After the content has been sent out a VerificationRequest can be started
/// with the `request_verification` method.
#[wasm_bindgen(js_name = "verificationRequestContent")]
pub fn verification_request_content(&self, methods: Option<Array>) -> Result<Promise, JsError> {
let me = self.inner.clone();
let methods =
methods.map(try_array_to_vec::<verification::VerificationMethod, _>).transpose()?;
Ok(future_to_promise(async move {
Ok(serde_json::to_string(&me.verification_request_content(methods).await)?)
}))
}
/// Get the master key of the identity.
#[wasm_bindgen(getter, js_name = "masterKey")]
pub fn master_key(&self) -> Result<String, JsError> {
let master_key = self.inner.master_key().as_ref();
Ok(serde_json::to_string(master_key)?)
}
/// Get the self-signing key of the identity.
#[wasm_bindgen(getter, js_name = "selfSigningKey")]
pub fn self_signing_key(&self) -> Result<String, JsError> {
let self_signing_key = self.inner.self_signing_key().as_ref();
Ok(serde_json::to_string(self_signing_key)?)
}
}
-40
View File
@@ -1,40 +0,0 @@
use js_sys::{Array, Object, Reflect};
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
/// A really hacky and dirty code to downcast a `JsValue` to `T:
/// RefFromWasmAbi`, inspired by
/// https://github.com/rustwasm/wasm-bindgen/issues/2231#issuecomment-656293288.
///
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
pub(crate) fn downcast<T>(value: &JsValue, classname: &str) -> Result<T::Anchor, JsError>
where
T: RefFromWasmAbi<Abi = u32>,
{
let constructor_name = Object::get_prototype_of(value).constructor().name();
if constructor_name == classname {
let pointer = Reflect::get(value, &JsValue::from_str("ptr"))
.map_err(|_| JsError::new("Failed to read the `JsValue` pointer"))?;
let pointer = pointer
.as_f64()
.ok_or_else(|| JsError::new("Failed to read the `JsValue` pointer as a `f64`"))?
as u32;
Ok(unsafe { T::ref_from_abi(pointer) })
} else {
Err(JsError::new(&format!(
"Expect an `{classname}` instance, received `{constructor_name}` instead",
)))
}
}
/// Transform a value `JS` from JavaScript to a Rust wrapper, to a
/// Rust wrapped type.
pub(crate) fn try_array_to_vec<JS, Rust>(
array: Array,
) -> Result<Vec<Rust>, <JS as TryFrom<JsValue>>::Error>
where
JS: TryFrom<JsValue> + Into<Rust>,
{
array.iter().map(|item| JS::try_from(item).map(Into::into)).collect()
}
-73
View File
@@ -1,73 +0,0 @@
// Copyright 2022 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.
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs, missing_debug_implementations)]
// triggered by wasm_bindgen code
#![allow(clippy::drop_non_drop)]
pub mod attachment;
pub mod device;
pub mod encryption;
pub mod events;
mod future;
pub mod identifiers;
pub mod identities;
mod js;
pub mod machine;
mod macros;
pub mod olm;
pub mod requests;
pub mod responses;
pub mod store;
pub mod sync_events;
mod tracing;
pub mod types;
pub mod verification;
pub mod vodozemac;
use js_sys::JsString;
use wasm_bindgen::prelude::*;
/// Object containing the versions of the Rust libraries we are using.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct Versions {
/// The version of the vodozemac crate.
#[wasm_bindgen(readonly)]
pub vodozemac: JsString,
/// The version of the matrix-sdk-crypto crate.
#[wasm_bindgen(readonly)]
pub matrix_sdk_crypto: JsString,
}
/// Get the versions of the Rust libraries we are using.
#[wasm_bindgen(js_name = "getVersions")]
pub fn get_versions() -> Versions {
Versions {
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.into(),
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.into(),
}
}
/// Run some stuff when the Wasm module is instantiated.
///
/// Right now, it does the following:
///
/// * Redirect Rust panics to JavaScript console.
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
}
@@ -1,865 +0,0 @@
//! The crypto specific Olm objects.
use std::{collections::BTreeMap, ops::Deref, time::Duration};
use futures_util::StreamExt;
use js_sys::{Array, Function, Map, Promise, Set};
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{json, Value as JsonValue};
use tracing::warn;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::{spawn_local, JsFuture};
use crate::{
device, encryption,
future::future_to_promise,
identifiers, identities,
js::downcast,
olm, requests,
requests::{OutgoingRequest, ToDeviceRequest},
responses::{self, response_from_string},
store,
store::RoomKeyInfo,
sync_events, types, verification, vodozemac,
};
/// State machine implementation of the Olm/Megolm encryption protocol
/// used for Matrix end to end encryption.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct OlmMachine {
inner: matrix_sdk_crypto::OlmMachine,
}
#[wasm_bindgen]
impl OlmMachine {
/// Constructor will always fail. To create a new `OlmMachine`, please use
/// the `initialize` method.
///
/// Why this pattern? `initialize` returns a `Promise`. Returning a
// `Promise` from a constructor is not idiomatic in JavaScript.
#[wasm_bindgen(constructor)]
pub fn new() -> Result<OlmMachine, JsError> {
Err(JsError::new("To build an `OlmMachine`, please use the `initialize` method"))
}
/// Create a new `OlmMachine`.
///
/// The created machine will keep the encryption keys either in a IndexedDB
/// based store, or in a memory store and once the objects is dropped,
/// the keys will be lost.
///
/// # Arguments
///
/// * `user_id` - represents the unique ID of the user that owns this
/// machine.
///
/// * `device_id` - represents the unique ID of the device
/// that owns this machine.
///
/// * `store_name` - The name that should be used to open the IndexedDB
/// based database. If this isn't provided, a memory-only store will be
/// used. *Note* the memory-only store will lose your E2EE keys when the
/// `OlmMachine` gets dropped.
///
/// * `store_passphrase` - The passphrase that should be used to encrypt the
/// IndexedDB based
pub fn initialize(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_name: Option<String>,
store_passphrase: Option<String>,
) -> Promise {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
future_to_promise(async move {
let store = match (store_name, store_passphrase) {
(Some(store_name), Some(mut store_passphrase)) => {
use zeroize::Zeroize;
let store = Some(
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_passphrase(
&store_name,
&store_passphrase,
)
.await?,
);
store_passphrase.zeroize();
store
}
(Some(store_name), None) => Some(
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_name(&store_name).await?,
),
(None, Some(_)) => {
return Err(anyhow::Error::msg(
"The `store_passphrase` has been set, but it has an effect only if \
`store_name` is set, which is not; please provide one",
))
}
(None, None) => None,
};
Ok(OlmMachine {
inner: match store {
// We need this `#[cfg]` because `IndexeddbCryptoStore`
// implements `CryptoStore` only on `target_arch =
// "wasm32"`. Without that, we could have a compilation
// error when checking the entire workspace. In practice,
// it doesn't impact this crate because it's always
// compiled for `wasm32`.
#[cfg(target_arch = "wasm32")]
Some(store) => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id.as_ref(),
device_id.as_ref(),
store,
)
.await?
}
_ => {
matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
.await
}
},
})
})
}
/// The unique user ID that owns this `OlmMachine` instance.
#[wasm_bindgen(getter, js_name = "userId")]
pub fn user_id(&self) -> identifiers::UserId {
identifiers::UserId::from(self.inner.user_id().to_owned())
}
/// The unique device ID that identifies this `OlmMachine`.
#[wasm_bindgen(getter, js_name = "deviceId")]
pub fn device_id(&self) -> identifiers::DeviceId {
identifiers::DeviceId::from(self.inner.device_id().to_owned())
}
/// Get the public parts of our Olm identity keys.
#[wasm_bindgen(getter, js_name = "identityKeys")]
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
self.inner.identity_keys().into()
}
/// Get the display name of our own device.
#[wasm_bindgen(getter, js_name = "displayName")]
pub fn display_name(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move { Ok(me.display_name().await?) })
}
/// Get the list of users whose devices we are currently tracking.
///
/// A user can be marked for tracking using the
/// [`update_tracked_users`](#method.update_tracked_users) method.
///
/// Returns a `Set<UserId>`.
#[wasm_bindgen(js_name = "trackedUsers")]
pub fn tracked_users(&self) -> Result<Promise, JsError> {
let set = Set::new(&JsValue::UNDEFINED);
let me = self.inner.clone();
Ok(future_to_promise(async move {
for user in me.tracked_users().await? {
set.add(&identifiers::UserId::from(user).into());
}
Ok(set)
}))
}
/// Update the list of tracked users.
///
/// The OlmMachine maintains a list of users whose devices we are keeping
/// track of: these are known as "tracked users". These must be users
/// that we share a room with, so that the server sends us updates for
/// their device lists.
///
/// # Arguments
///
/// * `users` - An array of user ids that should be added to the list of
/// tracked users
///
/// Any users that hadn't been seen before will be flagged for a key query
/// immediately, and whenever `receive_sync_changes` receives a
/// "changed" notification for that user in the future.
///
/// Users that were already in the list are unaffected.
#[wasm_bindgen(js_name = "updateTrackedUsers")]
pub fn update_tracked_users(&self, users: &Array) -> Result<Promise, JsError> {
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
me.update_tracked_users(users.iter().map(AsRef::as_ref)).await?;
Ok(JsValue::UNDEFINED)
}))
}
/// Handle to-device events and one-time key counts from a sync
/// response.
///
/// This will decrypt and handle to-device events returning the
/// decrypted versions of them.
///
/// To decrypt an event from the room timeline call
/// `decrypt_room_event`.
#[wasm_bindgen(js_name = "receiveSyncChanges")]
pub fn receive_sync_changes(
&self,
to_device_events: &str,
changed_devices: &sync_events::DeviceLists,
one_time_key_counts: &Map,
unused_fallback_keys: Option<Set>,
) -> Result<Promise, JsError> {
let to_device_events = serde_json::from_str(to_device_events)?;
let changed_devices = changed_devices.inner.clone();
let one_time_key_counts: BTreeMap<DeviceKeyAlgorithm, UInt> = one_time_key_counts
.entries()
.into_iter()
.filter_map(|js_value| {
let pair = Array::from(&js_value.ok()?);
let (key, value) = (
DeviceKeyAlgorithm::from(pair.at(0).as_string()?),
UInt::new(pair.at(1).as_f64()? as u64)?,
);
Some((key, value))
})
.collect();
// Convert the unused_fallback_keys JS Set to a `Vec<DeviceKeyAlgorithm>`
let unused_fallback_keys: Option<Vec<DeviceKeyAlgorithm>> =
unused_fallback_keys.map(|fallback_keys| {
fallback_keys
.values()
.into_iter()
.filter_map(|js_value| {
Some(DeviceKeyAlgorithm::from(js_value.ok()?.as_string()?))
})
.collect()
});
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.receive_sync_changes(
to_device_events,
&changed_devices,
&one_time_key_counts,
unused_fallback_keys.as_deref(),
)
.await?,
)?)
}))
}
/// Get the outgoing requests that need to be sent out.
///
/// This returns a list of `JsValue` to represent either:
/// * `KeysUploadRequest`,
/// * `KeysQueryRequest`,
/// * `KeysClaimRequest`,
/// * `ToDeviceRequest`,
/// * `SignatureUploadRequest`,
/// * `RoomMessageRequest` or
/// * `KeysBackupRequest`.
///
/// Those requests need to be sent out to the server and the
/// responses need to be passed back to the state machine using
/// `mark_request_as_sent`.
#[wasm_bindgen(js_name = "outgoingRequests")]
pub fn outgoing_requests(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(me
.outgoing_requests()
.await?
.into_iter()
.map(OutgoingRequest)
.map(TryFrom::try_from)
.collect::<Result<Vec<JsValue>, _>>()?
.into_iter()
.collect::<Array>())
})
}
/// Mark the request with the given request ID as sent (see
/// `outgoing_requests`).
///
/// Arguments are:
///
/// * `request_id` represents the unique ID of the request that was sent
/// out. This is needed to couple the response with the now sent out
/// request.
/// * `response_type` represents the type of the request that was sent out.
/// * `response` represents the response that was received from the server
/// after the outgoing request was sent out.
#[wasm_bindgen(js_name = "markRequestAsSent")]
pub fn mark_request_as_sent(
&self,
request_id: &str,
request_type: requests::RequestType,
response: &str,
) -> Result<Promise, JsError> {
let transaction_id = OwnedTransactionId::from(request_id);
let response = response_from_string(response)?;
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me.mark_request_as_sent(&transaction_id, &incoming_response).await.map(|_| true)?)
}))
}
/// Encrypt a room message for the given room.
///
/// Beware that a room key needs to be shared before this
/// method can be called using the `share_room_key` method.
///
/// `room_id` is the ID of the room for which the message should
/// be encrypted. `event_type` is the type of the event. `content`
/// is the plaintext content of the message that should be
/// encrypted.
///
/// # Panics
///
/// Panics if a group session for the given room wasn't shared
/// beforehand.
#[wasm_bindgen(js_name = "encryptRoomEvent")]
pub fn encrypt_room_event(
&self,
room_id: &identifiers::RoomId,
event_type: String,
content: &str,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let content: JsonValue = serde_json::from_str(content)?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(serde_json::to_string(
&me.encrypt_room_event_raw(&room_id, content, event_type.as_ref()).await?,
)?)
}))
}
/// Decrypt an event from a room timeline.
///
/// # Arguments
///
/// * `event`, the event that should be decrypted.
/// * `room_id`, the ID of the room where the event was sent to.
#[wasm_bindgen(js_name = "decryptRoomEvent")]
pub fn decrypt_room_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = room_id.inner.clone();
let me = self.inner.clone();
Ok(future_to_promise(async move {
let room_event = me.decrypt_room_event(&event, room_id.as_ref()).await?;
Ok(responses::DecryptedRoomEvent::from(room_event))
}))
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we
/// have stored locally.
#[wasm_bindgen(js_name = "crossSigningStatus")]
pub fn cross_signing_status(&self) -> Promise {
let me = self.inner.clone();
future_to_promise::<_, olm::CrossSigningStatus>(async move {
Ok(me.cross_signing_status().await.into())
})
}
/// Export all the private cross signing keys we have.
///
/// The export will contain the seeds for the ed25519 keys as
/// unpadded base64 encoded strings.
///
/// Returns `null` if we dont have any private cross signing keys;
/// otherwise returns a `CrossSigningKeyExport`.
#[wasm_bindgen(js_name = "exportCrossSigningKeys")]
pub fn export_cross_signing_keys(&self) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(me.export_cross_signing_keys().await.map(store::CrossSigningKeyExport::from))
})
}
/// Import our private cross signing keys.
///
/// The keys should be provided as unpadded-base64-encoded strings.
///
/// Returns a `CrossSigningStatus`.
#[wasm_bindgen(js_name = "importCrossSigningKeys")]
pub fn import_cross_signing_keys(
&self,
master_key: Option<String>,
self_signing_key: Option<String>,
user_signing_key: Option<String>,
) -> Promise {
let me = self.inner.clone();
let export = matrix_sdk_crypto::store::CrossSigningKeyExport {
master_key,
self_signing_key,
user_signing_key,
};
future_to_promise(async move {
Ok(me.import_cross_signing_keys(export).await.map(olm::CrossSigningStatus::from)?)
})
}
/// Create a new cross signing identity and get the upload request
/// to push the new public keys to the server.
///
/// Warning: This will delete any existing cross signing keys that
/// might exist on the server and thus will reset the trust
/// between all the devices.
///
/// Uploading these keys will require user interactive auth.
///
/// Returns an `Array` of `OutgoingRequest`s
#[wasm_bindgen(js_name = "bootstrapCrossSigning")]
pub fn bootstrap_cross_signing(&self, reset: bool) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
let (upload_signing_keys_request, upload_signatures_request) =
me.bootstrap_cross_signing(reset).await?;
let tuple = Array::new();
tuple.set(
0,
requests::SigningKeysUploadRequest::try_from(&upload_signing_keys_request)?.into(),
);
tuple.set(
1,
requests::SignatureUploadRequest::try_from(&upload_signatures_request)?.into(),
);
Ok(tuple)
})
}
/// Get the cross signing user identity of a user.
///
/// Returns a promise for an `OwnUserIdentity`, a `UserIdentity`, or
/// `undefined`.
#[wasm_bindgen(js_name = "getIdentity")]
pub fn get_identity(&self, user_id: &identifiers::UserId) -> Promise {
let me = self.inner.clone();
let user_id = user_id.inner.clone();
future_to_promise(async move {
Ok(me.get_identity(user_id.as_ref(), None).await?.map(identities::UserIdentities::from))
})
}
/// Sign the given message using our device key and if available
/// cross-signing master key.
pub fn sign(&self, message: String) -> Promise {
let me = self.inner.clone();
future_to_promise::<_, types::Signatures>(async move { Ok(me.sign(&message).await.into()) })
}
/// Invalidate the currently active outbound group session for the
/// given room.
///
/// Returns true if a session was invalidated, false if there was
/// no session to invalidate.
#[wasm_bindgen(js_name = "invalidateGroupSession")]
pub fn invalidate_group_session(&self, room_id: &identifiers::RoomId) -> Promise {
let room_id = room_id.inner.clone();
let me = self.inner.clone();
future_to_promise(async move { Ok(me.invalidate_group_session(&room_id).await?) })
}
/// Get to-device requests to share a room key with users in a room.
///
/// `room_id` is the room ID. `users` is an array of `UserId`
/// objects. `encryption_settings` are an `EncryptionSettings`
/// object.
///
/// Returns an array of `ToDeviceRequest`s.
#[wasm_bindgen(js_name = "shareRoomKey")]
pub fn share_room_key(
&self,
room_id: &identifiers::RoomId,
users: &Array,
encryption_settings: &encryption::EncryptionSettings,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let encryption_settings =
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
let me = self.inner.clone();
Ok(future_to_promise(async move {
let to_device_requests = me
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await?;
// convert each request to our own ToDeviceRequest struct, and then wrap it in a
// JsValue.
//
// Then collect the results into a javascript Array, throwing any errors into
// the promise.
Ok(to_device_requests
.into_iter()
.map(|td| ToDeviceRequest::try_from(td.deref()).map(JsValue::from))
.collect::<Result<Array, _>>()?)
}))
}
/// Get the a key claiming request for the user/device pairs that
/// we are missing Olm sessions for.
///
/// Returns `null` if no key claiming request needs to be sent
/// out, otherwise it returns a `KeysClaimRequest` object.
///
/// Sessions need to be established between devices so group
/// sessions for a room can be shared with them.
///
/// This should be called every time a group session needs to be
/// shared as well as between sync calls. After a sync some
/// devices may request room keys without us having a valid Olm
/// session with them, making it impossible to server the room key
/// request, thus its necessary to check for missing sessions
/// between sync as well.
///
/// Note: Care should be taken that only one such request at a
/// time is in flight, e.g. using a lock.
///
/// The response of a successful key claiming requests needs to be
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
///
/// `users` represents the list of users that we should check if
/// we lack a session with one of their devices. This can be an
/// empty iterator when calling this method between sync requests.
#[wasm_bindgen(js_name = "getMissingSessions")]
pub fn get_missing_sessions(&self, users: &Array) -> Result<Promise, JsError> {
let users = users
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
match me.get_missing_sessions(users.iter().map(AsRef::as_ref)).await? {
Some((transaction_id, keys_claim_request)) => {
Ok(JsValue::from(requests::KeysClaimRequest::try_from((
transaction_id.to_string(),
&keys_claim_request,
))?))
}
None => Ok(JsValue::NULL),
}
}))
}
/// Get a map holding all the devices of a user.
///
/// `user_id` represents the unique ID of the user that the
/// devices belong to.
#[wasm_bindgen(js_name = "getUserDevices")]
pub fn get_user_devices(&self, user_id: &identifiers::UserId) -> Promise {
let user_id = user_id.inner.clone();
let me = self.inner.clone();
future_to_promise::<_, device::UserDevices>(async move {
// wait for up to a second for any in-flight device list requests to complete.
// The reason for this isn't so much to avoid races (some level of raciness is
// inevitable for this method) but to make testing easier.
Ok(me.get_user_devices(&user_id, Some(Duration::from_secs(1))).await.map(Into::into)?)
})
}
/// Get a specific device of a user if one is found and the crypto store
/// didn't throw an error.
///
/// `user_id` represents the unique ID of the user that the
/// identity belongs to. `device_id` represents the unique ID of
/// the device.
#[wasm_bindgen(js_name = "getDevice")]
pub fn get_device(
&self,
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
) -> Promise {
let user_id = user_id.inner.clone();
let device_id = device_id.inner.clone();
let me = self.inner.clone();
future_to_promise::<_, Option<device::Device>>(async move {
Ok(me.get_device(&user_id, &device_id, None).await?.map(Into::into))
})
}
/// Get a verification object for the given user ID with the given
/// flow ID (a to-device request ID if the verification has been
/// requested by a to-device request, or a room event ID if the
/// verification has been requested by a room event).
///
/// It returns a “`Verification` object”, which is either a `Sas`
/// or `Qr` object.
#[wasm_bindgen(js_name = "getVerification")]
pub fn get_verification(
&self,
user_id: &identifiers::UserId,
flow_id: &str,
) -> Result<JsValue, JsError> {
self.inner
.get_verification(&user_id.inner, flow_id)
.map(verification::Verification)
.map(JsValue::try_from)
.transpose()
.map(JsValue::from)
}
/// Get a verification request object with the given flow ID.
#[wasm_bindgen(js_name = "getVerificationRequest")]
pub fn get_verification_request(
&self,
user_id: &identifiers::UserId,
flow_id: &str,
) -> Option<verification::VerificationRequest> {
self.inner.get_verification_request(&user_id.inner, flow_id).map(Into::into)
}
/// Get all the verification requests of a given user.
#[wasm_bindgen(js_name = "getVerificationRequests")]
pub fn get_verification_requests(&self, user_id: &identifiers::UserId) -> Array {
self.inner
.get_verification_requests(&user_id.inner)
.into_iter()
.map(verification::VerificationRequest::from)
.map(JsValue::from)
.collect()
}
/// Receive a verification event.
///
/// 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.
#[wasm_bindgen(js_name = "receiveVerificationEvent")]
pub fn receive_verification_event(
&self,
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let room_id = room_id.inner.clone();
let event: ruma::events::AnySyncMessageLikeEvent = serde_json::from_str(event)?;
let event = event.into_full_event(room_id);
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me.receive_verification_event(&event).await.map(|_| JsValue::UNDEFINED)?)
}))
}
/// Export the keys that match the given predicate.
///
/// `predicate` is a closure that will be called for every known
/// `InboundGroupSession`, which represents a room key. If the closure
/// returns `true`, the `InboundGroupSession` will be included in the
/// export, otherwise it won't.
#[wasm_bindgen(js_name = "exportRoomKeys")]
pub fn export_room_keys(&self, predicate: Function) -> Promise {
let me = self.inner.clone();
future_to_promise(async move {
Ok(serde_json::to_string(
&me.export_room_keys(|session| {
let session = session.clone();
predicate
.call1(&JsValue::NULL, &olm::InboundGroupSession::from(session).into())
.expect("Predicate function passed to `export_room_keys` failed")
.as_bool()
.unwrap_or(false)
})
.await?,
)?)
})
}
/// Import the given room keys into our store.
///
/// `exported_keys` is a list of previously exported keys that should be
/// imported into our store. If we already have a better version of a key,
/// the key will _not_ be imported.
///
/// `progress_listener` is a closure that takes 2 arguments: `progress` and
/// `total`, and returns nothing.
#[wasm_bindgen(js_name = "importRoomKeys")]
pub fn import_room_keys(
&self,
exported_room_keys: &str,
progress_listener: Function,
) -> Result<Promise, JsError> {
let me = self.inner.clone();
let exported_room_keys: Vec<matrix_sdk_crypto::olm::ExportedRoomKey> =
serde_json::from_str(exported_room_keys)?;
Ok(future_to_promise(async move {
let matrix_sdk_crypto::RoomKeyImportResult { imported_count, total_count, keys } = me
.import_room_keys(exported_room_keys, false, |progress, total| {
let progress: u64 = progress.try_into().unwrap();
let total: u64 = total.try_into().unwrap();
progress_listener
.call2(&JsValue::NULL, &JsValue::from(progress), &JsValue::from(total))
.expect("Progress listener passed to `import_room_keys` failed");
})
.await?;
Ok(serde_json::to_string(&json!({
"imported_count": imported_count,
"total_count": total_count,
"keys": keys,
}))?)
}))
}
/// Encrypt the list of exported room keys using the given passphrase.
///
/// `exported_room_keys` is a list of sessions that should be encrypted
/// (it's generally returned by `export_room_keys`). `passphrase` is the
/// passphrase that will be used to encrypt the exported room keys. And
/// `rounds` is the number of rounds that should be used for the key
/// derivation when the passphrase gets turned into an AES key. More rounds
/// are increasingly computationnally intensive and as such help against
/// brute-force attacks. Should be at least `10_000`, while values in the
/// `100_000` ranges should be preferred.
#[wasm_bindgen(js_name = "encryptExportedRoomKeys")]
pub fn encrypt_exported_room_keys(
exported_room_keys: &str,
passphrase: &str,
rounds: u32,
) -> Result<String, JsError> {
let exported_room_keys: Vec<matrix_sdk_crypto::olm::ExportedRoomKey> =
serde_json::from_str(exported_room_keys)?;
Ok(matrix_sdk_crypto::encrypt_room_key_export(&exported_room_keys, passphrase, rounds)?)
}
/// Try to decrypt a reader into a list of exported room keys.
///
/// `encrypted_exported_room_keys` is the result from
/// `encrypt_exported_room_keys`. `passphrase` is the passphrase that was
/// used when calling `encrypt_exported_room_keys`.
#[wasm_bindgen(js_name = "decryptExportedRoomKeys")]
pub fn decrypt_exported_room_keys(
encrypted_exported_room_keys: &str,
passphrase: &str,
) -> Result<String, JsError> {
Ok(serde_json::to_string(&matrix_sdk_crypto::decrypt_room_key_export(
encrypted_exported_room_keys.as_bytes(),
passphrase,
)?)?)
}
/// Register a callback which will be called whenever there is an update to
/// a room key.
///
/// `callback` should be a function that takes a single argument (an array
/// of {@link RoomKeyInfo}) and returns a Promise.
#[wasm_bindgen(js_name = "registerRoomKeyUpdatedCallback")]
pub async fn register_room_key_updated_callback(&self, callback: Function) {
let stream = self.inner.store().room_keys_received_stream();
// fire up a promise chain which will call `cb` on each result from the stream
spawn_local(async move {
// take a reference to `callback` (which we then pass into the closure), to stop
// the callback being moved into the closure (which would mean we could only
// call the closure once)
let callback_ref = &callback;
stream.for_each(move |item| send_room_key_info_to_callback(callback_ref, item)).await;
});
}
/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called.
///
/// All associated resources will be closed too, like IndexedDB
/// connections.
pub fn close(self) {}
}
// helper for register_room_key_received_callback: wraps the key info
// into our own RoomKeyInfo struct, and passes it into the javascript
// function
async fn send_room_key_info_to_callback(
callback: &Function,
room_key_info: Vec<matrix_sdk_crypto::store::RoomKeyInfo>,
) {
let rki: Array = room_key_info.into_iter().map(RoomKeyInfo::from).map(JsValue::from).collect();
match promise_result_to_future(callback.call1(&JsValue::NULL, &rki)).await {
Ok(_) => (),
Err(e) => {
warn!("Error calling room-key-received callback: {:?}", e);
}
}
}
/// Given a result from a javascript function which returns a Promise (or throws
/// an exception before returning one), convert the result to a rust Future
/// which completes with the result of the promise
pub(crate) async fn promise_result_to_future(
res: Result<JsValue, JsValue>,
) -> Result<JsValue, JsValue> {
match res {
Ok(retval) => {
if !retval.has_type::<Promise>() {
panic!("not a promise");
}
let prom: Promise = retval.dyn_into().map_err(|v| {
JsError::new(&format!("function returned a non-Promise value {v:?}"))
})?;
JsFuture::from(prom).await
}
Err(e) => {
// the function threw an exception before it returned the promise. We can just
// return the error as an error result.
Err(e)
}
}
}
@@ -1,33 +0,0 @@
/// We have the following pattern quite often in our code:
///
/// ```rust
/// struct Foo {
/// inner: Bar,
/// }
///
/// impl From<Bar> for Foo {
/// fn from(inner: Bar) -> Self {
/// Self { inner }
/// }
/// }
/// ```
///
/// Because I feel lazy, let's do a macro to write this:
///
/// ```rust
/// struct Foo {
/// inner: Bar,
/// }
///
/// impl_from_to_inner!(Bar => Foo);
/// ```
#[macro_export]
macro_rules! impl_from_to_inner {
($from:ty => $to:ty) => {
impl From<$from> for $to {
fn from(inner: $from) -> Self {
Self { inner }
}
}
};
}
-79
View File
@@ -1,79 +0,0 @@
//! Olm types.
use wasm_bindgen::prelude::*;
use crate::{identifiers, impl_from_to_inner, vodozemac::Curve25519PublicKey};
/// Struct representing the state of our private cross signing keys,
/// it shows which private cross signing keys we have locally stored.
#[wasm_bindgen]
#[derive(Debug)]
pub struct CrossSigningStatus {
inner: matrix_sdk_crypto::olm::CrossSigningStatus,
}
impl_from_to_inner!(matrix_sdk_crypto::olm::CrossSigningStatus => CrossSigningStatus);
#[wasm_bindgen]
impl CrossSigningStatus {
/// Do we have the master key?
#[wasm_bindgen(getter, js_name = "hasMaster")]
pub fn has_master(&self) -> bool {
self.inner.has_master
}
/// Do we have the self signing key? This one is necessary to sign
/// our own devices.
#[wasm_bindgen(getter, js_name = "hasSelfSigning")]
pub fn has_self_signing(&self) -> bool {
self.inner.has_self_signing
}
/// Do we have the user signing key? This one is necessary to sign
/// other users.
#[wasm_bindgen(getter, js_name = "hasUserSigning")]
pub fn has_user_signing(&self) -> bool {
self.inner.has_user_signing
}
}
/// Inbound group session.
///
/// Inbound group sessions are used to exchange room messages between a group of
/// participants. Inbound group sessions are used to decrypt the room messages.
#[wasm_bindgen]
#[derive(Debug)]
pub struct InboundGroupSession {
inner: matrix_sdk_crypto::olm::InboundGroupSession,
}
impl_from_to_inner!(matrix_sdk_crypto::olm::InboundGroupSession => InboundGroupSession);
#[wasm_bindgen]
impl InboundGroupSession {
/// The room where this session is used in.
#[wasm_bindgen(getter, js_name = "roomId")]
pub fn room_id(&self) -> identifiers::RoomId {
self.inner.room_id().to_owned().into()
}
/// The Curve25519 key of the sender of this session, as a
/// [Curve25519PublicKey].
#[wasm_bindgen(getter, js_name = "senderKey")]
pub fn sender_key(&self) -> Curve25519PublicKey {
self.inner.sender_key().into()
}
/// Returns the unique identifier for this session.
#[wasm_bindgen(getter, js_name = "sessionId")]
pub fn session_id(&self) -> String {
self.inner.session_id().to_owned()
}
/// Has the session been imported from a file or server-side backup? As
/// opposed to being directly received as an `m.room_key` event.
#[wasm_bindgen(js_name = "hasBeenImported")]
pub fn has_been_imported(&self) -> bool {
self.inner.has_been_imported()
}
}
@@ -1,503 +0,0 @@
//! Types to handle requests.
use js_sys::JsString;
use matrix_sdk_crypto::{
requests::{
KeysBackupRequest as OriginalKeysBackupRequest,
KeysQueryRequest as OriginalKeysQueryRequest,
RoomMessageRequest as OriginalRoomMessageRequest,
ToDeviceRequest as OriginalToDeviceRequest,
UploadSigningKeysRequest as OriginalUploadSigningKeysRequest,
},
OutgoingRequests,
};
use ruma::{
api::client::keys::{
claim_keys::v3::Request as OriginalKeysClaimRequest,
upload_keys::v3::Request as OriginalKeysUploadRequest,
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
},
events::EventContent,
};
use wasm_bindgen::prelude::*;
/** Outgoing Requests * */
/// Data for a request to the `/keys/upload` API endpoint
/// ([specification]).
///
/// Publishes end-to-end encryption keys for the device.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
/// `one_time_keys`, `fallback_keys`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysUploadRequest {
/// Create a new `KeysUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
Self { id: Some(id), body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysUpload
}
}
/// Data for a request to the `/keys/query` API endpoint
/// ([specification]).
///
/// Returns the current devices and identity keys for the given users.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysQueryRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `timeout`,
/// `device_keys`, `token`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysQueryRequest {
/// Create a new `KeysQueryRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
Self { id: Some(id), body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysQuery
}
}
/// Data for a request to the `/keys/claim` API endpoint
/// ([specification]).
///
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
/// sessions.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysClaimRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `timeout`,
/// `one_time_keys`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysClaimRequest {
/// Create a new `KeysClaimRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
Self { id: Some(id), body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysClaim
}
}
/// Data for a request to the `/sendToDevice` API endpoint
/// ([specification]).
///
/// Send an event to a single device or to a group of devices.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct ToDeviceRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A string representing the type of event being sent to each devices.
#[wasm_bindgen(readonly)]
pub event_type: JsString,
/// A string representing a request identifier unique to the access token
/// used to send the request.
#[wasm_bindgen(readonly)]
pub txn_id: JsString,
/// A JSON-encoded string containing the rest of the payload: `messages`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl ToDeviceRequest {
/// Create a new `ToDeviceRequest`.
#[wasm_bindgen(constructor)]
pub fn new(
id: JsString,
event_type: JsString,
txn_id: JsString,
body: JsString,
) -> ToDeviceRequest {
Self { id: Some(id), event_type, txn_id, body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::ToDevice
}
}
/// Data for a request to the `/keys/signatures/upload` API endpoint
/// ([specification]).
///
/// Publishes cross-signing signatures for the user.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct SignatureUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the payload of the request
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly, js_name = "body")]
pub signed_keys: JsString,
}
#[wasm_bindgen]
impl SignatureUploadRequest {
/// Create a new `SignatureUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, signed_keys: JsString) -> SignatureUploadRequest {
Self { id: Some(id), signed_keys }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::SignatureUpload
}
}
/// A customized owned request type for sending out room messages
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct RoomMessageRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A string representing the room to send the event to.
#[wasm_bindgen(readonly)]
pub room_id: JsString,
/// A string representing the transaction ID for this event.
///
/// Clients should generate an ID unique across requests with the same
/// access token; it will be used by the server to ensure idempotency of
/// requests.
#[wasm_bindgen(readonly)]
pub txn_id: JsString,
/// A string representing the type of event to be sent.
#[wasm_bindgen(readonly)]
pub event_type: JsString,
/// A JSON-encoded string containing the message's content.
#[wasm_bindgen(readonly, js_name = "body")]
pub content: JsString,
}
#[wasm_bindgen]
impl RoomMessageRequest {
/// Create a new `RoomMessageRequest`.
#[wasm_bindgen(constructor)]
pub fn new(
id: JsString,
room_id: JsString,
txn_id: JsString,
event_type: JsString,
content: JsString,
) -> RoomMessageRequest {
Self { id: Some(id), room_id, txn_id, event_type, content }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::RoomMessage
}
}
/// A request that will back up a batch of room keys to the server
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
#[derive(Debug)]
#[wasm_bindgen(getter_with_clone)]
pub struct KeysBackupRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `rooms`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl KeysBackupRequest {
/// Create a new `KeysBackupRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysBackupRequest {
Self { id: Some(id), body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysBackup
}
}
/** Other Requests * */
/// Request that will publish a cross signing identity.
///
/// This uploads the public cross signing key triplet.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct SigningKeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: Option<JsString>,
/// A JSON-encoded string containing the rest of the payload: `master_key`,
/// `self_signing_key`, `user_signing_key`.
///
/// It represents the body of the HTTP request.
#[wasm_bindgen(readonly)]
pub body: JsString,
}
#[wasm_bindgen]
impl SigningKeysUploadRequest {
/// Create a new `SigningKeysUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> SigningKeysUploadRequest {
Self { id: Some(id), body }
}
/// Get its request type.
#[wasm_bindgen(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::SigningKeysUpload
}
}
macro_rules! request {
(
$destination_request:ident from $source_request:ident
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
$( $( and )? groups $( $grouped_field_name:ident ),+ $(,)? )?
) => {
impl TryFrom<&$source_request> for $destination_request {
type Error = serde_json::Error;
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
request!(
@__try_from $destination_request from $source_request
(request_id = None, request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name, )+ ] )?
)
}
}
impl TryFrom<(String, &$source_request)> for $destination_request {
type Error = serde_json::Error;
fn try_from(
(request_id, request): (String, &$source_request),
) -> Result<Self, Self::Error> {
request!(
@__try_from $destination_request from $source_request
(request_id = Some(request_id.into()), request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name, )+ ] )?
)
}
}
};
(
@__try_from $destination_request:ident from $source_request:ident
(request_id = $request_id:expr, request = $request:expr)
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
$( groups [ $( $grouped_field_name:ident ),* $(,)? ] )?
) => {
{
Ok($destination_request {
id: $request_id,
$(
$(
$field_name: request!(@__field $field_name : $field_type ; request = $request),
)*
)?
$(
body: {
let mut map = serde_json::Map::new();
$(
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(&$request.$grouped_field_name).unwrap());
)*
let object = serde_json::Value::Object(map);
serde_json::to_string(&object)?.into()
}
)?
})
}
};
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
};
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
$request.$field_name.to_string().into()
};
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
serde_json::to_string(&$request.$field_name)?.into()
};
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
$request.content.event_type().to_string().into()
};
}
// Outgoing Requests
request!(KeysUploadRequest from OriginalKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from OriginalKeysQueryRequest groups timeout, device_keys, token);
request!(KeysClaimRequest from OriginalKeysClaimRequest groups timeout, one_time_keys);
request!(ToDeviceRequest from OriginalToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
request!(SignatureUploadRequest from OriginalSignatureUploadRequest extracts signed_keys: json);
request!(RoomMessageRequest from OriginalRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
request!(KeysBackupRequest from OriginalKeysBackupRequest groups rooms);
// Other Requests
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest groups master_key, self_signing_key, user_signing_key);
// JavaScript has no complex enums like Rust. To return structs of
// different types, we have no choice that hiding everything behind a
// `JsValue`.
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
impl TryFrom<OutgoingRequest> for JsValue {
type Error = serde_json::Error;
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
let request_id = outgoing_request.0.request_id().to_string();
Ok(match outgoing_request.0.request() {
OutgoingRequests::KeysUpload(request) => {
JsValue::from(KeysUploadRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysQuery(request) => {
JsValue::from(KeysQueryRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysClaim(request) => {
JsValue::from(KeysClaimRequest::try_from((request_id, request))?)
}
OutgoingRequests::ToDeviceRequest(request) => {
JsValue::from(ToDeviceRequest::try_from((request_id, request))?)
}
OutgoingRequests::SignatureUpload(request) => {
JsValue::from(SignatureUploadRequest::try_from((request_id, request))?)
}
OutgoingRequests::RoomMessage(request) => {
JsValue::from(RoomMessageRequest::try_from((request_id, request))?)
}
OutgoingRequests::KeysBackup(request) => {
JsValue::from(KeysBackupRequest::try_from((request_id, request))?)
}
})
}
}
/// Represent the type of a request.
#[wasm_bindgen]
#[derive(Debug)]
pub enum RequestType {
/// Represents a `KeysUploadRequest`.
KeysUpload,
/// Represents a `KeysQueryRequest`.
KeysQuery,
/// Represents a `KeysClaimRequest`.
KeysClaim,
/// Represents a `ToDeviceRequest`.
ToDevice,
/// Represents a `SignatureUploadRequest`.
SignatureUpload,
/// Represents a `RoomMessageRequest`.
RoomMessage,
/// Represents a `KeysBackupRequest`.
KeysBackup,
/// Represents a `SigningKeysUploadRequest`
SigningKeysUpload,
}
@@ -1,247 +0,0 @@
//! Types related to responses.
use js_sys::{Array, JsString};
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
pub(crate) use ruma::api::client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
upload_keys::v3::Response as KeysUploadResponse,
upload_signatures::v3::Response as SignatureUploadResponse,
upload_signing_keys::v3::Response as SigningKeysUploadResponse,
},
message::send_message_event::v3::Response as RoomMessageResponse,
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
};
use ruma::api::IncomingResponse as RumaIncomingResponse;
use wasm_bindgen::prelude::*;
use crate::{encryption, identifiers, requests::RequestType};
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
http::Response::builder().status(200).body(body.as_bytes().to_vec())
}
/// Intermediate private type to store an incoming owned response,
/// without the need to manage lifetime.
pub(crate) enum OwnedResponse {
KeysUpload(KeysUploadResponse),
KeysQuery(KeysQueryResponse),
KeysClaim(KeysClaimResponse),
ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse),
RoomMessage(RoomMessageResponse),
KeysBackup(KeysBackupResponse),
SigningKeysUpload(SigningKeysUploadResponse),
}
impl From<KeysUploadResponse> for OwnedResponse {
fn from(response: KeysUploadResponse) -> Self {
OwnedResponse::KeysUpload(response)
}
}
impl From<KeysQueryResponse> for OwnedResponse {
fn from(response: KeysQueryResponse) -> Self {
OwnedResponse::KeysQuery(response)
}
}
impl From<KeysClaimResponse> for OwnedResponse {
fn from(response: KeysClaimResponse) -> Self {
OwnedResponse::KeysClaim(response)
}
}
impl From<ToDeviceResponse> for OwnedResponse {
fn from(response: ToDeviceResponse) -> Self {
OwnedResponse::ToDevice(response)
}
}
impl From<SignatureUploadResponse> for OwnedResponse {
fn from(response: SignatureUploadResponse) -> Self {
Self::SignatureUpload(response)
}
}
impl From<RoomMessageResponse> for OwnedResponse {
fn from(response: RoomMessageResponse) -> Self {
OwnedResponse::RoomMessage(response)
}
}
impl From<KeysBackupResponse> for OwnedResponse {
fn from(r: KeysBackupResponse) -> Self {
Self::KeysBackup(r)
}
}
impl From<SigningKeysUploadResponse> for OwnedResponse {
fn from(response: SigningKeysUploadResponse) -> OwnedResponse {
Self::SigningKeysUpload(response)
}
}
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
type Error = JsError;
/// Convert an HTTP response object into the underlying ruma model of the
/// response, and wrap as an OwnedResponse.
///
/// (This is used in
/// `matrix_sdk_crypto_js::OlmMachine::mark_request_as_sent`.)
///
/// # Arguments
///
/// * `request_type` - the type of the request that got this response
/// * `response` - the raw HTTP response
fn try_from(
(request_type, response): (RequestType, http::Response<Vec<u8>>),
) -> Result<Self, Self::Error> {
match request_type {
RequestType::KeysUpload => {
KeysUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysQuery => {
KeysQueryResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysClaim => {
KeysClaimResponse::try_from_http_response(response).map(Into::into)
}
RequestType::ToDevice => {
ToDeviceResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::RoomMessage => {
RoomMessageResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysBackup => {
KeysBackupResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SigningKeysUpload => {
// SigningKeysUploadResponse::try_from_http_response returns a
// FromHttpResponseError<UiaaResponse> instead of a
// ruma_client_api::error::Error, so we have to handle it
// separately. In practice, the application is supposed to
// have dealt with UIA before we get here, so we just map it straight into a
// regular `JsError` anyway.
return SigningKeysUploadResponse::try_from_http_response(response)
.map(Into::into)
.map_err(JsError::from);
}
}
.map_err(JsError::from)
}
}
// Make `OwnedResponse` implement `Into<IncomingResponse>`.
//
// Required so that we can pass `OwnedResponse` into the real
// `matrix_sdk_crypto_js::OlmMachine::mark_request_as_sent`.
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
fn from(response: &'a OwnedResponse) -> Self {
match response {
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
OwnedResponse::SigningKeysUpload(response) => {
IncomingResponse::SigningKeysUpload(response)
}
}
}
}
/// A decrypted room event.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct DecryptedRoomEvent {
/// The JSON-encoded decrypted event.
#[wasm_bindgen(readonly)]
pub event: JsString,
encryption_info: Option<EncryptionInfo>,
}
#[wasm_bindgen]
impl DecryptedRoomEvent {
/// The user ID of the event sender, note this is untrusted data
/// unless the `verification_state` is as well trusted.
#[wasm_bindgen(getter)]
pub fn sender(&self) -> Option<identifiers::UserId> {
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
}
/// The device ID of the device that sent us the event, note this
/// is untrusted data unless `verification_state` is as well
/// trusted.
#[wasm_bindgen(getter, js_name = "senderDevice")]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
}
/// The Curve25519 key of the device that created the megolm
/// decryption key originally.
#[wasm_bindgen(getter, js_name = "senderCurve25519Key")]
pub fn sender_curve25519_key(&self) -> Option<JsString> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone().into(),
})
}
/// The signing Ed25519 key that have created the megolm key that
/// was used to decrypt this session.
#[wasm_bindgen(getter, js_name = "senderClaimedEd25519Key")]
pub fn sender_claimed_ed25519_key(&self) -> Option<JsString> {
match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned().map(Into::into)
}
}
}
/// Chain of Curve25519 keys through which this session was
/// forwarded, via `m.forwarded_room_key` events.
#[wasm_bindgen(getter, js_name = "forwardingCurve25519KeyChain")]
pub fn forwarding_curve25519_key_chain(&self) -> Array {
Array::new()
}
/// The verification state of the device that sent us the event,
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[wasm_bindgen(js_name = "shieldState")]
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
let state = &self.encryption_info.as_ref()?.verification_state;
if strict {
Some(state.to_shield_state_strict().into())
} else {
Some(state.to_shield_state_lax().into())
}
}
}
impl From<matrix_sdk_common::deserialized_responses::TimelineEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::TimelineEvent) -> Self {
Self {
event: value.event.json().get().to_owned().into(),
encryption_info: value.encryption_info,
}
}
}
@@ -1,76 +0,0 @@
//! Store types.
use wasm_bindgen::prelude::*;
use crate::{
encryption::EncryptionAlgorithm, identifiers::RoomId, impl_from_to_inner,
vodozemac::Curve25519PublicKey,
};
/// A struct containing private cross signing keys that can be backed
/// up or uploaded to the secret store.
#[wasm_bindgen]
#[derive(Debug)]
pub struct CrossSigningKeyExport {
pub(crate) inner: matrix_sdk_crypto::store::CrossSigningKeyExport,
}
impl_from_to_inner!(matrix_sdk_crypto::store::CrossSigningKeyExport => CrossSigningKeyExport);
#[wasm_bindgen]
impl CrossSigningKeyExport {
/// The seed of the master key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "masterKey")]
pub fn master_key(&self) -> Option<String> {
self.inner.master_key.clone()
}
/// The seed of the self signing key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "self_signing_key")]
pub fn self_signing_key(&self) -> Option<String> {
self.inner.self_signing_key.clone()
}
/// The seed of the user signing key encoded as unpadded base64.
#[wasm_bindgen(getter, js_name = "userSigningKey")]
pub fn user_signing_key(&self) -> Option<String> {
self.inner.user_signing_key.clone()
}
}
/// Information on a room key that has been received or imported.
#[wasm_bindgen]
#[derive(Debug)]
pub struct RoomKeyInfo {
pub(crate) inner: matrix_sdk_crypto::store::RoomKeyInfo,
}
impl_from_to_inner!(matrix_sdk_crypto::store::RoomKeyInfo => RoomKeyInfo);
#[wasm_bindgen]
impl RoomKeyInfo {
/// The {@link EncryptionAlgorithm} that this key is used for. Will be one
/// of the `m.megolm.*` algorithms.
#[wasm_bindgen(getter)]
pub fn algorithm(&self) -> EncryptionAlgorithm {
self.inner.algorithm.clone().into()
}
/// The room where the key is used.
#[wasm_bindgen(getter, js_name = "roomId")]
pub fn room_id(&self) -> RoomId {
self.inner.room_id.clone().into()
}
/// The Curve25519 key of the device which initiated the session originally.
#[wasm_bindgen(getter, js_name = "senderKey")]
pub fn sender_key(&self) -> Curve25519PublicKey {
self.inner.sender_key.into()
}
/// The ID of the session that the key is for.
#[wasm_bindgen(getter, js_name = "sessionId")]
pub fn session_id(&self) -> String {
self.inner.session_id.clone()
}
}
@@ -1,69 +0,0 @@
//! `GET /_matrix/client/*/sync`
use js_sys::Array;
use wasm_bindgen::prelude::*;
use crate::{identifiers, js::downcast};
/// Information on E2E device updates.
#[wasm_bindgen]
#[derive(Debug)]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
}
#[wasm_bindgen]
impl DeviceLists {
/// Create an empty `DeviceLists`.
///
/// `changed` and `left` must be an array of `UserId`.
#[wasm_bindgen(constructor)]
pub fn new(changed: Option<Array>, left: Option<Array>) -> Result<DeviceLists, JsError> {
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
inner.changed = changed
.unwrap_or_default()
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
inner.left = left
.unwrap_or_default()
.iter()
.map(|user| Ok(downcast::<identifiers::UserId>(&user, "UserId")?.inner.clone()))
.collect::<Result<Vec<ruma::OwnedUserId>, JsError>>()?;
Ok(Self { inner })
}
/// Returns true if there are no device list updates.
#[wasm_bindgen(js_name = "isEmpty")]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// List of users who have updated their device identity keys or
/// who now share an encrypted room with the client since the
/// previous sync
#[wasm_bindgen(getter)]
pub fn changed(&self) -> Array {
self.inner
.changed
.iter()
.map(|user| identifiers::UserId::from(user.clone()))
.map(JsValue::from)
.collect()
}
/// List of users who no longer share encrypted rooms since the
/// previous sync response.
#[wasm_bindgen(getter)]
pub fn left(&self) -> Array {
self.inner
.left
.iter()
.map(|user| identifiers::UserId::from(user.clone()))
.map(JsValue::from)
.collect()
}
}
@@ -1,287 +0,0 @@
use wasm_bindgen::prelude::*;
/// Logger level.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub enum LoggerLevel {
/// `TRACE` level.
///
/// Designate very low priority, often extremely verbose,
/// information.
Trace,
/// `DEBUG` level.
///
/// Designate lower priority information.
Debug,
/// `INFO` level.
///
/// Designate useful information.
Info,
/// `WARN` level.
///
/// Designate hazardous situations.
Warn,
/// `ERROR` level.
///
/// Designate very serious errors.
Error,
}
#[cfg(feature = "tracing")]
mod inner {
use std::{
fmt,
fmt::Write as _,
sync::{Arc, Once},
};
use tracing::{
field::{Field, Visit},
metadata::LevelFilter,
Event, Level, Metadata, Subscriber,
};
use tracing_subscriber::{
layer::{Context, Layer as TracingLayer},
prelude::*,
reload, Registry,
};
use super::*;
type TracingInner = Arc<reload::Handle<Layer, Registry>>;
/// Type to install and to manipulate the tracing layer.
#[wasm_bindgen]
pub struct Tracing {
handle: TracingInner,
}
impl fmt::Debug for Tracing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Tracing").finish_non_exhaustive()
}
}
#[wasm_bindgen]
impl Tracing {
/// Check whether the `tracing` feature has been enabled.
#[wasm_bindgen(js_name = "isAvailable")]
pub fn is_available() -> bool {
true
}
/// Install the tracing layer.
///
/// `Tracing` is a singleton. Once it is installed,
/// consecutive calls to the constructor will construct a new
/// `Tracing` object but with the exact same inner
/// state. Calling the constructor with a new `min_level` will
/// just update the `min_level` parameter; in that regard, it
/// is similar to calling the `min_level` method on an
/// existing `Tracing` object.
#[wasm_bindgen(constructor)]
pub fn new(min_level: LoggerLevel) -> Result<Tracing, JsError> {
static mut INSTALL: Option<TracingInner> = None;
static INSTALLED: Once = Once::new();
INSTALLED.call_once(|| {
let (filter, reload_handle) = reload::Layer::new(Layer::new(min_level.clone()));
tracing_subscriber::registry().with(filter).init();
unsafe { INSTALL = Some(Arc::new(reload_handle)) };
});
let tracing = Tracing {
handle: unsafe { INSTALL.as_ref() }
.cloned()
.expect("`Tracing` has not been installed correctly"),
};
// If it's not the first call to `install`, the
// `min_level` can be different. Let's update it.
tracing.min_level(min_level);
Ok(tracing)
}
/// Re-define the minimum logger level.
#[wasm_bindgen(setter, js_name = "minLevel")]
pub fn min_level(&self, min_level: LoggerLevel) {
let _ = self.handle.modify(|layer| layer.min_level = min_level.into());
}
/// Turn the logger on, i.e. it emits logs again if it was turned
/// off.
#[wasm_bindgen(js_name = "turnOn")]
pub fn turn_on(&self) {
let _ = self.handle.modify(|layer| layer.turn_on());
}
/// Turn the logger off, i.e. it no long emits logs.
#[wasm_bindgen(js_name = "turnOff")]
pub fn turn_off(&self) {
let _ = self.handle.modify(|layer| layer.turn_off());
}
}
impl From<LoggerLevel> for Level {
fn from(value: LoggerLevel) -> Self {
use LoggerLevel::*;
match value {
Trace => Self::TRACE,
Debug => Self::DEBUG,
Info => Self::INFO,
Warn => Self::WARN,
Error => Self::ERROR,
}
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = "debug")]
fn log_debug(message: String);
#[wasm_bindgen(js_namespace = console, js_name = "info")]
fn log_info(message: String);
#[wasm_bindgen(js_namespace = console, js_name = "warn")]
fn log_warn(message: String);
#[wasm_bindgen(js_namespace = console, js_name = "error")]
fn log_error(message: String);
}
struct Layer {
min_level: Level,
enabled: bool,
}
impl Layer {
fn new<L>(min_level: L) -> Self
where
L: Into<Level>,
{
Self { min_level: min_level.into(), enabled: true }
}
fn turn_on(&mut self) {
self.enabled = true;
}
fn turn_off(&mut self) {
self.enabled = false;
}
}
impl<S> TracingLayer<S> for Layer
where
S: Subscriber,
{
fn enabled(&self, metadata: &Metadata<'_>, _: Context<'_, S>) -> bool {
self.enabled && metadata.level() <= &self.min_level
}
fn max_level_hint(&self) -> Option<LevelFilter> {
if !self.enabled {
Some(LevelFilter::OFF)
} else {
Some(LevelFilter::from_level(self.min_level))
}
}
fn on_event(&self, event: &Event<'_>, _: Context<'_, S>) {
let mut recorder = StringVisitor::new();
event.record(&mut recorder);
let metadata = event.metadata();
let level = metadata.level();
let origin = metadata
.file()
.and_then(|file| metadata.line().map(|ln| format!("{file}:{ln}")))
.unwrap_or_default();
let message = format!("{level} {origin}{recorder}");
match *level {
Level::TRACE => log_debug(message),
Level::DEBUG => log_debug(message),
Level::INFO => log_info(message),
Level::WARN => log_warn(message),
Level::ERROR => log_error(message),
}
}
}
struct StringVisitor {
string: String,
}
impl StringVisitor {
fn new() -> Self {
Self { string: String::new() }
}
}
impl Visit for StringVisitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
match field.name() {
"message" => {
if !self.string.is_empty() {
self.string.push('\n');
}
let _ = write!(self.string, "{value:?}");
}
field_name => {
let _ = write!(self.string, "\n{field_name} = {value:?}");
}
}
}
}
impl fmt::Display for StringVisitor {
fn fmt(&self, mut f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !self.string.is_empty() {
write!(&mut f, " {}", self.string)
} else {
Ok(())
}
}
}
}
#[cfg(not(feature = "tracing"))]
mod inner {
use super::*;
/// Type to install and to manipulate the tracing layer.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Tracing;
#[wasm_bindgen]
impl Tracing {
/// Check whether the `tracing` feature has been enabled.
#[wasm_bindgen(js_name = "isAvailable")]
pub fn is_available() -> bool {
false
}
/// The `tracing` feature is not enabled, so this constructor
/// will raise an error.
#[wasm_bindgen(constructor)]
pub fn new(_min_level: LoggerLevel) -> Result<Tracing, JsError> {
Err(JsError::new("The `tracing` feature is disabled. Check `Tracing.isAvailable` before constructing `Tracing`"))
}
}
}
pub use inner::*;
-156
View File
@@ -1,156 +0,0 @@
//! Extra types, like `Signatures`.
use js_sys::Map;
use wasm_bindgen::prelude::*;
use crate::{
identifiers::{DeviceKeyId, UserId},
impl_from_to_inner,
vodozemac::Ed25519Signature,
};
/// A collection of `Signature`.
#[wasm_bindgen]
#[derive(Debug, Default)]
pub struct Signatures {
inner: matrix_sdk_crypto::types::Signatures,
}
impl_from_to_inner!(matrix_sdk_crypto::types::Signatures => Signatures);
#[wasm_bindgen]
impl Signatures {
/// Creates a new, empty, signatures collection.
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
matrix_sdk_crypto::types::Signatures::new().into()
}
/// Add the given signature from the given signer and the given key ID to
/// the collection.
#[wasm_bindgen(js_name = "addSignature")]
pub fn add_signature(
&mut self,
signer: &UserId,
key_id: &DeviceKeyId,
signature: &Ed25519Signature,
) -> Option<MaybeSignature> {
self.inner
.add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner)
.map(Into::into)
}
/// Try to find an Ed25519 signature from the given signer with
/// the given key ID.
#[wasm_bindgen(js_name = "getSignature")]
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
}
/// Get the map of signatures that belong to the given user.
pub fn get(&self, signer: &UserId) -> Option<Map> {
let map = Map::new();
for (device_key_id, maybe_signature) in
self.inner.get(signer.inner.as_ref()).map(|map| {
map.iter().map(|(device_key_id, maybe_signature)| {
(
device_key_id.as_str().to_owned(),
MaybeSignature::from(maybe_signature.clone()),
)
})
})?
{
map.set(&device_key_id.into(), &maybe_signature.into());
}
Some(map)
}
/// Remove all the signatures we currently hold.
pub fn clear(&mut self) {
self.inner.clear();
}
/// Do we hold any signatures or is our collection completely
/// empty.
#[wasm_bindgen(js_name = "isEmpty")]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// How many signatures do we currently hold.
#[wasm_bindgen(getter)]
pub fn count(&self) -> usize {
self.inner.signature_count()
}
}
/// Represents a potentially decoded signature (but not a validated
/// one).
#[wasm_bindgen]
#[derive(Debug)]
pub struct Signature {
inner: matrix_sdk_crypto::types::Signature,
}
impl_from_to_inner!(matrix_sdk_crypto::types::Signature => Signature);
#[wasm_bindgen]
impl Signature {
/// Get the Ed25519 signature, if this is one.
#[wasm_bindgen(getter)]
pub fn ed25519(&self) -> Option<Ed25519Signature> {
self.inner.ed25519().map(Into::into)
}
/// Convert the signature to a base64 encoded string.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
type MaybeSignatureInner =
Result<matrix_sdk_crypto::types::Signature, matrix_sdk_crypto::types::InvalidSignature>;
/// Represents a signature that is either valid _or_ that could not be
/// decoded.
#[wasm_bindgen]
#[derive(Debug)]
pub struct MaybeSignature {
inner: MaybeSignatureInner,
}
impl_from_to_inner!(MaybeSignatureInner => MaybeSignature);
#[wasm_bindgen]
impl MaybeSignature {
/// Check whether the signature has been successfully decoded.
#[wasm_bindgen(js_name = "isValid")]
pub fn is_valid(&self) -> bool {
self.inner.is_ok()
}
/// Check whether the signature could not be successfully decoded.
#[wasm_bindgen(js_name = "isInvalid")]
pub fn is_invalid(&self) -> bool {
self.inner.is_err()
}
/// The signature, if successfully decoded.
#[wasm_bindgen(getter)]
pub fn signature(&self) -> Option<Signature> {
self.inner.as_ref().cloned().map(Into::into).ok()
}
/// The base64 encoded string that is claimed to contain a
/// signature but could not be decoded, if any.
#[wasm_bindgen(getter, js_name = "invalidSignatureSource")]
pub fn invalid_signature_source(&self) -> Option<String> {
match &self.inner {
Ok(_) => None,
Err(signature) => Some(signature.source.clone()),
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,193 +0,0 @@
//! Vodozemac types.
use wasm_bindgen::prelude::*;
use crate::impl_from_to_inner;
/// An Ed25519 public key, used to verify digital signatures.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Ed25519PublicKey {
inner: vodozemac::Ed25519PublicKey,
}
#[wasm_bindgen]
impl Ed25519PublicKey {
/// The number of bytes an Ed25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Ed25519PublicKey::LENGTH
}
/// Serialize an Ed25519 public key to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
impl_from_to_inner!(vodozemac::Ed25519PublicKey => Ed25519PublicKey);
/// An Ed25519 digital signature, can be used to verify the
/// authenticity of a message.
#[wasm_bindgen]
#[derive(Debug)]
pub struct Ed25519Signature {
pub(crate) inner: vodozemac::Ed25519Signature,
}
impl_from_to_inner!(vodozemac::Ed25519Signature => Ed25519Signature);
#[wasm_bindgen]
impl Ed25519Signature {
/// Try to create an Ed25519 signature from an unpadded base64
/// representation.
#[wasm_bindgen(constructor)]
pub fn new(signature: String) -> Result<Ed25519Signature, JsError> {
Ok(Self { inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())? })
}
/// Serialize a Ed25519 signature to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// A Curve25519 public key.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Curve25519PublicKey {
inner: vodozemac::Curve25519PublicKey,
}
#[wasm_bindgen]
impl Curve25519PublicKey {
/// The number of bytes a Curve25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Curve25519PublicKey::LENGTH
}
/// Serialize an Curve25519 public key to an unpadded base64
/// representation.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
impl_from_to_inner!(vodozemac::Curve25519PublicKey => Curve25519PublicKey);
/// Struct holding the two public identity keys of an account.
#[wasm_bindgen(getter_with_clone)]
#[derive(Debug)]
pub struct IdentityKeys {
/// The Ed25519 public key, used for signing.
pub ed25519: Ed25519PublicKey,
/// The Curve25519 public key, used for establish shared secrets.
pub curve25519: Curve25519PublicKey,
}
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
Self {
ed25519: Ed25519PublicKey { inner: value.ed25519 },
curve25519: Curve25519PublicKey { inner: value.curve25519 },
}
}
}
/// An enum over the different key types a device can have.
///
/// Currently devices have a curve25519 and ed25519 keypair. The keys
/// transport format is a base64 encoded string, any unknown key type
/// will be left as such a string.
#[wasm_bindgen]
#[derive(Debug)]
pub struct DeviceKey {
inner: matrix_sdk_crypto::types::DeviceKey,
}
impl_from_to_inner!(matrix_sdk_crypto::types::DeviceKey => DeviceKey);
#[wasm_bindgen]
impl DeviceKey {
/// Get the name of the device key.
#[wasm_bindgen(getter)]
pub fn name(&self) -> DeviceKeyName {
(&self.inner).into()
}
/// Get the value associated to the `Curve25519` device key name.
#[wasm_bindgen(getter)]
pub fn curve25519(&self) -> Option<Curve25519PublicKey> {
use matrix_sdk_crypto::types::DeviceKey::*;
match &self.inner {
Curve25519(key) => Some((*key).into()),
_ => None,
}
}
/// Get the value associated to the `Ed25519` device key name.
#[wasm_bindgen(getter)]
pub fn ed25519(&self) -> Option<Ed25519PublicKey> {
use matrix_sdk_crypto::types::DeviceKey::*;
match &self.inner {
Ed25519(key) => Some((*key).into()),
_ => None,
}
}
/// Get the value associated to the `Unknown` device key name.
#[wasm_bindgen(getter)]
pub fn unknown(&self) -> Option<String> {
use matrix_sdk_crypto::types::DeviceKey::*;
match &self.inner {
Unknown(key) => Some(key.clone()),
_ => None,
}
}
/// Convert the `DeviceKey` into a base64 encoded string.
#[wasm_bindgen(js_name = "toBase64")]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
impl From<&matrix_sdk_crypto::types::DeviceKey> for DeviceKeyName {
fn from(device_key: &matrix_sdk_crypto::types::DeviceKey) -> Self {
use matrix_sdk_crypto::types::DeviceKey::*;
match device_key {
Curve25519(_) => Self::Curve25519,
Ed25519(_) => Self::Ed25519,
Unknown(_) => Self::Unknown,
}
}
}
/// An enum over the different key types a device can have.
///
/// Currently devices have a curve25519 and ed25519 keypair. The keys
/// transport format is a base64 encoded string, any unknown key type
/// will be left as such a string.
#[wasm_bindgen]
#[derive(Debug)]
pub enum DeviceKeyName {
/// The curve25519 device key.
Curve25519,
/// The ed25519 device key.
Ed25519,
/// An unknown device key.
Unknown,
}
@@ -1,17 +0,0 @@
const { UserId, initAsync } = require("../pkg/matrix_sdk_crypto_js");
test("can instantiate rust objects with async initialiser", async () => {
initUserId = () => new UserId("@foo:bar.org");
// stub out the synchronous WebAssembly loader with one that raises an error
jest.spyOn(WebAssembly, "Module").mockImplementation(() => {
throw new Error("synchronous WebAssembly.Module() not allowed");
});
// this should fail
expect(initUserId).toThrow(/synchronous/);
// but once we init with async, it should work
await initAsync();
initUserId();
});
@@ -1,83 +0,0 @@
const { Attachment, EncryptedAttachment } = require("../pkg/matrix_sdk_crypto_js");
describe(Attachment.name, () => {
const originalData = "hello";
const textEncoder = new TextEncoder();
const textDecoder = new TextDecoder();
let encryptedAttachment;
test("can encrypt data", () => {
encryptedAttachment = Attachment.encrypt(textEncoder.encode(originalData));
const mediaEncryptionInfo = JSON.parse(encryptedAttachment.mediaEncryptionInfo);
expect(mediaEncryptionInfo).toMatchObject({
v: "v2",
key: {
kty: expect.any(String),
key_ops: expect.arrayContaining(["encrypt", "decrypt"]),
alg: expect.any(String),
k: expect.any(String),
ext: expect.any(Boolean),
},
iv: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
hashes: {
sha256: expect.stringMatching(/^[A-Za-z0-9\+/]+$/),
},
});
const encryptedData = encryptedAttachment.encryptedData;
expect(
encryptedData.every((i) => {
i != 0;
}),
).toStrictEqual(false);
});
test("can decrypt data", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
const decryptedAttachment = Attachment.decrypt(encryptedAttachment);
expect(textDecoder.decode(decryptedAttachment)).toStrictEqual(originalData);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
test("can only decrypt once", () => {
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
expect(() => {
textDecoder.decode(decryptedAttachment);
}).toThrow();
});
});
describe(EncryptedAttachment.name, () => {
const originalData = "hello";
const textDecoder = new TextDecoder();
test("can be created manually", () => {
const encryptedAttachment = new EncryptedAttachment(
new Uint8Array([24, 150, 67, 37, 144]),
JSON.stringify({
v: "v2",
key: {
kty: "oct",
key_ops: ["encrypt", "decrypt"],
alg: "A256CTR",
k: "QbNXUjuukFyEJ8cQZjJuzN6mMokg0HJIjx0wVMLf5BM",
ext: true,
},
iv: "xk2AcWkomiYAAAAAAAAAAA",
hashes: {
sha256: "JsRbDXgOja4xvDiF3DwBuLHdxUzIrVYIuj7W/t3aEok",
},
}),
);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(false);
expect(textDecoder.decode(Attachment.decrypt(encryptedAttachment))).toStrictEqual(originalData);
expect(encryptedAttachment.hasMediaEncryptionInfoBeenConsumed).toStrictEqual(true);
});
});
File diff suppressed because it is too large Load Diff
@@ -1,35 +0,0 @@
const {
EncryptionAlgorithm,
EncryptionSettings,
HistoryVisibility,
VerificationState,
} = require("../pkg/matrix_sdk_crypto_js");
describe("EncryptionAlgorithm", () => {
test("has the correct variant values", () => {
expect(EncryptionAlgorithm.OlmV1Curve25519AesSha2).toStrictEqual(0);
expect(EncryptionAlgorithm.MegolmV1AesSha2).toStrictEqual(1);
});
});
describe(EncryptionSettings.name, () => {
test("can be instantiated with default values", () => {
const es = new EncryptionSettings();
expect(es.algorithm).toStrictEqual(EncryptionAlgorithm.MegolmV1AesSha2);
expect(es.rotationPeriod).toStrictEqual(604800000000n);
expect(es.rotationPeriodMessages).toStrictEqual(100n);
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Shared);
});
test("checks the history visibility values", () => {
const es = new EncryptionSettings();
es.historyVisibility = HistoryVisibility.Invited;
expect(es.historyVisibility).toStrictEqual(HistoryVisibility.Invited);
expect(() => {
es.historyVisibility = 42;
}).toThrow();
});
});
@@ -1,10 +0,0 @@
const { HistoryVisibility } = require("../pkg/matrix_sdk_crypto_js");
describe("HistoryVisibility", () => {
test("has the correct variant values", () => {
expect(HistoryVisibility.Invited).toStrictEqual(0);
expect(HistoryVisibility.Joined).toStrictEqual(1);
expect(HistoryVisibility.Shared).toStrictEqual(2);
expect(HistoryVisibility.WorldReadable).toStrictEqual(3);
});
});
@@ -1,92 +0,0 @@
const { DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest } = require("../pkg/matrix_sdk_crypto_js");
function* zip(...arrays) {
const len = Math.min(...arrays.map((array) => array.length));
for (let nth = 0; nth < len; ++nth) {
yield [...arrays.map((array) => array.at(nth))];
}
}
// Add a machine to another machine, i.e. be sure a machine knows
// another exists.
async function addMachineToMachine(machineToAdd, machine) {
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(
await machineToAdd.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual([]);
const outgoingRequests = await machineToAdd.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
let keysUploadRequest;
// Read the `KeysUploadRequest`.
{
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
expect(outgoingRequests[0].body).toBeDefined();
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
expect(body.one_time_keys).toBeDefined();
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
one_time_key_counts: {
curve25519: 10,
signed_curve25519: 20,
},
});
const marked = await machineToAdd.markRequestAsSent(
outgoingRequests[0].id,
outgoingRequests[0].type,
hypothetical_response,
);
expect(marked).toStrictEqual(true);
keysUploadRequest = outgoingRequests[0];
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
// Let's forge a `KeysQuery`'s response.
let keyQueryResponse = {
device_keys: {},
master_keys: {},
self_signing_keys: {},
user_signing_keys: {},
};
const userId = machineToAdd.userId.toString();
const deviceId = machineToAdd.deviceId.toString();
keyQueryResponse.device_keys[userId] = {};
keyQueryResponse.device_keys[userId][deviceId] = JSON.parse(keysUploadRequest.body).device_keys;
const keys = JSON.parse(signingKeysUploadRequest.body);
keyQueryResponse.master_keys[userId] = keys.master_key;
keyQueryResponse.self_signing_keys[userId] = keys.self_signing_key;
keyQueryResponse.user_signing_keys[userId] = keys.user_signing_key;
const marked = await machine.markRequestAsSent(
outgoingRequests[1].id,
outgoingRequests[1].type,
JSON.stringify(keyQueryResponse),
);
expect(marked).toStrictEqual(true);
}
}
module.exports = {
zip,
addMachineToMachine,
};
@@ -1,197 +0,0 @@
const {
DeviceId,
DeviceKeyAlgorithm,
DeviceKeyAlgorithmName,
DeviceKeyId,
EventId,
RoomId,
ServerName,
UserId,
} = require("../pkg/matrix_sdk_crypto_js");
describe(UserId.name, () => {
test("cannot be invalid", () => {
expect(() => {
new UserId("@foobar");
}).toThrow();
});
const user = new UserId("@foo:bar.org");
test("localpart is present", () => {
expect(user.localpart).toStrictEqual("foo");
});
test("server name is present", () => {
expect(user.serverName).toBeInstanceOf(ServerName);
});
test("user ID is not historical", () => {
expect(user.isHistorical()).toStrictEqual(false);
});
test("can read the user ID as a string", () => {
expect(user.toString()).toStrictEqual("@foo:bar.org");
});
});
describe(DeviceId.name, () => {
const device = new DeviceId("foo");
test("can read the device ID as a string", () => {
expect(device.toString()).toStrictEqual("foo");
});
});
describe(DeviceKeyId.name, () => {
for (const deviceKey of [
{
name: "ed25519",
id: "ed25519:foobar",
algorithmName: DeviceKeyAlgorithmName.Ed25519,
algorithm: "ed25519",
deviceId: "foobar",
},
{
name: "curve25519",
id: "curve25519:foobar",
algorithmName: DeviceKeyAlgorithmName.Curve25519,
algorithm: "curve25519",
deviceId: "foobar",
},
{
name: "signed curve25519",
id: "signed_curve25519:foobar",
algorithmName: DeviceKeyAlgorithmName.SignedCurve25519,
algorithm: "signed_curve25519",
deviceId: "foobar",
},
{
name: "unknown",
id: "hello:foobar",
algorithmName: DeviceKeyAlgorithmName.Unknown,
algorithm: "hello",
deviceId: "foobar",
},
]) {
test(`${deviceKey.name} algorithm`, () => {
const dk = new DeviceKeyId(deviceKey.id);
expect(dk.algorithm.name).toStrictEqual(deviceKey.algorithmName);
expect(dk.algorithm.toString()).toStrictEqual(deviceKey.algorithm);
expect(dk.deviceId.toString()).toStrictEqual(deviceKey.deviceId);
expect(dk.toString()).toStrictEqual(deviceKey.id);
});
}
});
describe("DeviceKeyAlgorithmName", () => {
test("has the correct variants", () => {
expect(DeviceKeyAlgorithmName.Ed25519).toStrictEqual(0);
expect(DeviceKeyAlgorithmName.Curve25519).toStrictEqual(1);
expect(DeviceKeyAlgorithmName.SignedCurve25519).toStrictEqual(2);
expect(DeviceKeyAlgorithmName.Unknown).toStrictEqual(3);
});
});
describe(RoomId.name, () => {
test("cannot be invalid", () => {
expect(() => {
new RoomId("!foo");
}).toThrow();
});
const room = new RoomId("!foo:bar.org");
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("foo");
});
test("server name is present", () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("!foo:bar.org");
});
});
describe(ServerName.name, () => {
test("cannot be invalid", () => {
expect(() => {
new ServerName("@foobar");
}).toThrow();
});
test("host is present", () => {
expect(new ServerName("foo.org").host).toStrictEqual("foo.org");
});
test("port can be optional", () => {
expect(new ServerName("foo.org").port).toStrictEqual(undefined);
expect(new ServerName("foo.org:1234").port).toStrictEqual(1234);
});
test("server is not an IP literal", () => {
expect(new ServerName("foo.org").isIpLiteral()).toStrictEqual(false);
});
});
describe(EventId.name, () => {
test("cannot be invalid", () => {
expect(() => {
new EventId("%foo");
}).toThrow();
});
describe("Versions 1 & 2", () => {
const room = new EventId("$h29iv0s8:foo.org");
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("h29iv0s8");
});
test("server name is present", () => {
expect(room.serverName).toBeInstanceOf(ServerName);
});
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$h29iv0s8:foo.org");
});
});
describe("Version 3", () => {
const room = new EventId("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
});
test("server name is present", () => {
expect(room.serverName).toBeUndefined();
});
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk");
});
});
describe("Version 4", () => {
const room = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
test("localpart is present", () => {
expect(room.localpart).toStrictEqual("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
});
test("server name is present", () => {
expect(room.serverName).toBeUndefined();
});
test("can read the room ID as string", () => {
expect(room.toString()).toStrictEqual("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
});
});
});
@@ -1,906 +0,0 @@
const {
CrossSigningStatus,
DecryptedRoomEvent,
DeviceId,
DeviceKeyId,
DeviceLists,
EncryptionSettings,
EventId,
InboundGroupSession,
KeysQueryRequest,
KeysUploadRequest,
MaybeSignature,
OlmMachine,
OwnUserIdentity,
RequestType,
RoomId,
RoomMessageRequest,
ShieldColor,
SignatureUploadRequest,
ToDeviceRequest,
UserId,
UserIdentity,
VerificationRequest,
VerificationState,
Versions,
getVersions,
} = require("../pkg/matrix_sdk_crypto_js");
const { addMachineToMachine } = require("./helper");
require("fake-indexeddb/auto");
describe("Versions", () => {
test("can find out the crate versions", async () => {
const versions = getVersions();
expect(versions).toBeInstanceOf(Versions);
expect(versions.vodozemac).toBeDefined();
expect(versions.matrix_sdk_crypto).toBeDefined();
});
});
describe(OlmMachine.name, () => {
test("can be instantiated with the async initializer", async () => {
expect(await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"))).toBeInstanceOf(OlmMachine);
});
test("can be instantiated with a store", async () => {
let store_name = "hello";
let store_passphrase = "world";
const by_store_name = (db) => db.name.startsWith(store_name);
// No databases.
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
// Creating a new Olm machine.
expect(
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
).toBeInstanceOf(OlmMachine);
// Oh, there is 2 databases now, prefixed by `store_name`.
let databases = (await indexedDB.databases()).filter(by_store_name);
expect(databases).toHaveLength(2);
expect(databases).toStrictEqual([
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
]);
// Creating a new Olm machine, with the stored state.
expect(
await OlmMachine.initialize(new UserId("@foo:bar.org"), new DeviceId("baz"), store_name, store_passphrase),
).toBeInstanceOf(OlmMachine);
// Same number of databases.
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(2);
});
describe("cannot be instantiated with a store", () => {
test("store name is missing", async () => {
let store_name = null;
let store_passphrase = "world";
let err = null;
try {
await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
} catch (error) {
err = error;
}
expect(err).toBeDefined();
});
test("store passphrase is missing", async () => {
let store_name = "hello";
let store_passphrase = null;
let err = null;
try {
await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
} catch (error) {
err = error;
}
expect(err).toBeDefined();
});
});
const user = new UserId("@alice:example.org");
const device = new DeviceId("foobar");
const room = new RoomId("!baz:matrix.org");
function machine(new_user, new_device) {
return OlmMachine.initialize(new_user || user, new_device || device);
}
test("can drop/close", async () => {
m = await machine();
m.close();
});
test("can drop/close with a store", async () => {
let store_name = "temporary";
let store_passphrase = "temporary";
const by_store_name = (db) => db.name.startsWith(store_name);
// No databases.
expect((await indexedDB.databases()).filter(by_store_name)).toHaveLength(0);
// Creating a new Olm machine.
const m = await OlmMachine.initialize(
new UserId("@foo:bar.org"),
new DeviceId("baz"),
store_name,
store_passphrase,
);
expect(m).toBeInstanceOf(OlmMachine);
// Oh, there is 2 databases now, prefixed by `store_name`.
let databases = (await indexedDB.databases()).filter(by_store_name);
expect(databases).toHaveLength(2);
expect(databases).toStrictEqual([
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 3 },
]);
// Let's force to close the `OlmMachine`.
m.close();
// Now we can delete the databases!
for (const database_name of [`${store_name}::matrix-sdk-crypto`, `${store_name}::matrix-sdk-crypto-meta`]) {
const deleting = indexedDB.deleteDatabase(database_name);
deleting.onsuccess = () => {};
deleting.onerror = () => {
throw new Error("failed to remove the database (error)");
};
deleting.onblocked = () => {
throw new Error("failed to remove the database (blocked)");
};
}
});
test("can read user ID", async () => {
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
});
test("can read device ID", async () => {
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
});
test("can read identity keys", async () => {
const identityKeys = (await machine()).identityKeys;
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
});
test("can read display name", async () => {
expect(await machine().displayName).toBeUndefined();
});
test("can read tracked users", async () => {
const m = await machine();
const trackedUsers = await m.trackedUsers();
expect(trackedUsers).toBeInstanceOf(Set);
expect(trackedUsers.size).toStrictEqual(0);
});
test("can update tracked users", async () => {
const m = await machine();
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
});
test("can receive sync changes", async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual([]);
});
test("can receive sync changes with unusedFallbackKeys as undefined", async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const receiveSyncChanges = JSON.parse(
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, undefined),
);
expect(receiveSyncChanges).toEqual([]);
});
test("can get the outgoing requests that need to be send out", async () => {
const m = await machine();
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = JSON.parse(
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys),
);
expect(receiveSyncChanges).toEqual([]);
const outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
{
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
expect(outgoingRequests[0].id).toBeDefined();
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
expect(outgoingRequests[0].body).toBeDefined();
const body = JSON.parse(outgoingRequests[0].body);
expect(body.device_keys).toBeDefined();
expect(body.one_time_keys).toBeDefined();
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
expect(outgoingRequests[1].id).toBeDefined();
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
expect(outgoingRequests[1].body).toBeDefined();
const body = JSON.parse(outgoingRequests[1].body);
expect(body.timeout).toBeDefined();
expect(body.device_keys).toBeDefined();
expect(body.token).toBeDefined();
}
});
describe("setup workflow to mark requests as sent", () => {
let m;
let ougoingRequests;
beforeAll(async () => {
m = await machine(new UserId("@alice:example.org"), new DeviceId("DEVICEID"));
const toDeviceEvents = JSON.stringify([]);
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = new Map();
const unusedFallbackKeys = new Set();
const receiveSyncChanges = await m.receiveSyncChanges(
toDeviceEvents,
changedDevices,
oneTimeKeyCounts,
unusedFallbackKeys,
);
outgoingRequests = await m.outgoingRequests();
expect(outgoingRequests).toHaveLength(2);
});
test("can mark requests as sent", async () => {
{
const request = outgoingRequests[0];
expect(request).toBeInstanceOf(KeysUploadRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
const hypothetical_response = JSON.stringify({
one_time_key_counts: {
curve25519: 10,
signed_curve25519: 20,
},
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
{
const request = outgoingRequests[1];
expect(request).toBeInstanceOf(KeysQueryRequest);
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
const hypothetical_response = JSON.stringify({
device_keys: {
"@alice:example.org": {
JLAFKJWSCS: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "JLAFKJWSCS",
keys: {
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM",
},
signatures: {
"@alice:example.org": {
"ed25519:JLAFKJWSCS":
"m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA",
},
},
unsigned: {
device_display_name: "Alice's mobile phone",
},
user_id: "@alice:example.org",
},
},
},
failures: {},
});
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
expect(marked).toStrictEqual(true);
}
});
});
describe("setup workflow to encrypt/decrypt events", () => {
let m;
const user = new UserId("@alice:example.org");
const device = new DeviceId("JLAFKJWSCS");
const room = new RoomId("!test:localhost");
beforeAll(async () => {
m = await machine(user, device);
});
test("can pass keysquery and keysclaim requests directly", async () => {
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
const hypothetical_response = JSON.stringify({
device_keys: {
"@example:localhost": {
AFGUOBTZWM: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "AFGUOBTZWM",
keys: {
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec",
},
signatures: {
"@example:localhost": {
"ed25519:AFGUOBTZWM":
"RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA",
},
},
user_id: "@example:localhost",
unsigned: {
device_display_name: "rust-sdk",
},
},
},
},
failures: {},
master_keys: {
"@example:localhost": {
user_id: "@example:localhost",
usage: ["master"],
keys: {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU",
},
signatures: {
"@example:localhost": {
"ed25519:TCSJXPWGVS":
"+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg",
},
},
},
},
self_signing_keys: {
"@example:localhost": {
user_id: "@example:localhost",
usage: ["self_signing"],
keys: {
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI":
"kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI",
},
signatures: {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA",
},
},
},
},
user_signing_keys: {
"@example:localhost": {
user_id: "@example:localhost",
usage: ["user_signing"],
keys: {
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s":
"g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s",
},
signatures: {
"@example:localhost": {
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU":
"nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg",
},
},
},
},
});
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
}
{
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
const hypothetical_response = JSON.stringify({
one_time_keys: {
"@example:localhost": {
AFGUOBTZWM: {
"signed_curve25519:AAAABQ": {
key: "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
signatures: {
"@example:localhost": {
"ed25519:AFGUOBTZWM":
"2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg",
},
},
},
},
},
},
failures: {},
});
const marked = await m.markRequestAsSent("bar", RequestType.KeysClaim, hypothetical_response);
}
});
test("can share a room key", async () => {
const other_users = [new UserId("@example:localhost")];
const requests = await m.shareRoomKey(room, other_users, new EncryptionSettings());
expect(requests).toHaveLength(1);
expect(requests[0]).toBeInstanceOf(ToDeviceRequest);
expect(requests[0].event_type).toEqual("m.room.encrypted");
expect(requests[0].txn_id).toBeDefined();
const content = JSON.parse(requests[0].body);
expect(Object.keys(content.messages)).toEqual(["@example:localhost"]);
});
let encrypted;
test("can encrypt an event", async () => {
encrypted = JSON.parse(
await m.encryptRoomEvent(
room,
"m.room.message",
JSON.stringify({
msgtype: "m.text",
body: "Hello, World!",
}),
),
);
expect(encrypted.algorithm).toBeDefined();
expect(encrypted.ciphertext).toBeDefined();
expect(encrypted.sender_key).toBeDefined();
expect(encrypted.device_id).toStrictEqual(device.toString());
expect(encrypted.session_id).toBeDefined();
});
test("can decrypt an event", async () => {
const decrypted = await m.decryptRoomEvent(
JSON.stringify({
type: "m.room.encrypted",
event_id: "$xxxxx:example.org",
origin_server_ts: Date.now(),
sender: user.toString(),
content: encrypted,
unsigned: {
age: 1234,
},
}),
room,
);
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
const event = JSON.parse(decrypted.event);
expect(event.content.msgtype).toStrictEqual("m.text");
expect(event.content.body).toStrictEqual("Hello, World!");
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
expect(decrypted.senderCurve25519Key).toBeDefined();
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
expect(decrypted.shieldState(true).color).toStrictEqual(ShieldColor.Red);
expect(decrypted.shieldState(false).color).toStrictEqual(ShieldColor.Red);
});
});
test("failure to decrypt returns a valid error", async () => {
const m = await machine();
const evt = {
type: "m.room.encrypted",
event_id: "$xxxxx:example.org",
origin_server_ts: Date.now(),
sender: user.toString(),
content: {
algorithm: "m.megolm.v1.aes-sha2",
ciphertext: "blah",
},
};
await expect(() => m.decryptRoomEvent(JSON.stringify(evt), room)).rejects.toThrowError();
});
test("can read cross-signing status", async () => {
const m = await machine();
const crossSigningStatus = await m.crossSigningStatus();
expect(crossSigningStatus).toBeInstanceOf(CrossSigningStatus);
expect(crossSigningStatus.hasMaster).toStrictEqual(false);
expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false);
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
});
test("can sign a message", async () => {
const m = await machine();
const signatures = await m.sign("foo");
expect(signatures.isEmpty()).toStrictEqual(false);
expect(signatures.count).toStrictEqual(1);
let base64;
// `get`
{
const signature = signatures.get(user);
expect(signature.has("ed25519:foobar")).toStrictEqual(true);
const s = signature.get("ed25519:foobar");
expect(s).toBeInstanceOf(MaybeSignature);
expect(s.isValid()).toStrictEqual(true);
expect(s.isInvalid()).toStrictEqual(false);
expect(s.invalidSignatureSource).toBeUndefined();
base64 = s.signature.toBase64();
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
expect(s.signature.ed25519.toBase64()).toStrictEqual(base64);
}
// `getSignature`
{
const signature = signatures.getSignature(user, new DeviceKeyId("ed25519:foobar"));
expect(signature.toBase64()).toStrictEqual(base64);
}
// Unknown signatures.
{
expect(signatures.get(new UserId("@hello:example.org"))).toBeUndefined();
expect(signatures.getSignature(user, new DeviceKeyId("world:foobar"))).toBeUndefined();
}
});
test("can get a user identities", async () => {
const m = await machine();
let _ = m.bootstrapCrossSigning(true);
const identity = await m.getIdentity(user);
expect(identity).toBeInstanceOf(OwnUserIdentity);
const masterKey = JSON.parse(identity.masterKey);
const selfSigningKey = JSON.parse(identity.selfSigningKey);
const userSigningKey = JSON.parse(identity.userSigningKey);
const masterObjKeys = Object.keys(masterKey.keys);
const keyFromMasterKey = masterKey.keys[masterObjKeys[0]];
// self signing key exists
expect(Object.keys(selfSigningKey.keys).length).toBe(1);
// self signing key is different from the master key
expect(selfSigningKey.keys[keyFromMasterKey]).not.toBeDefined();
const selfSigningObjKeys = Object.keys(selfSigningKey.keys);
const keyFromSelfSigningKey = masterKey.keys[selfSigningObjKeys[0]];
// user signing key exists
expect(Object.keys(userSigningKey.keys).length).toBe(1);
// user signing key is different from the master key
expect(userSigningKey.keys[keyFromMasterKey]).not.toBeDefined();
// user signing key is different from the self signing key
expect(userSigningKey.keys[keyFromSelfSigningKey]).not.toBeDefined();
const signatureUploadRequest = await identity.verify();
expect(signatureUploadRequest).toBeInstanceOf(SignatureUploadRequest);
const [verificationRequest, outgoingVerificationRequest] = await identity.requestVerification();
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
const isTrusted = await identity.trustsOurOwnDevice();
expect(isTrusted).toStrictEqual(false);
});
describe("can export/import room keys", () => {
let exportedRoomKeys;
test("can export room keys", async () => {
let m = await machine();
await m.shareRoomKey(room, [new UserId("@bob:example.org")], new EncryptionSettings());
exportedRoomKeys = await m.exportRoomKeys((session) => {
expect(session).toBeInstanceOf(InboundGroupSession);
expect(session.senderKey.toBase64()).toEqual(m.identityKeys.curve25519.toBase64());
expect(session.roomId.toString()).toStrictEqual(room.toString());
expect(session.sessionId).toBeDefined();
expect(session.hasBeenImported()).toStrictEqual(false);
return true;
});
const roomKeys = JSON.parse(exportedRoomKeys);
expect(roomKeys).toHaveLength(1);
expect(roomKeys[0]).toMatchObject({
algorithm: expect.any(String),
room_id: room.toString(),
sender_key: expect.any(String),
session_id: expect.any(String),
session_key: expect.any(String),
sender_claimed_keys: {
ed25519: expect.any(String),
},
forwarding_curve25519_key_chain: [],
});
});
let encryptedExportedRoomKeys;
let encryptionPassphrase = "Hello, Matrix!";
test("can encrypt the exported room keys", () => {
encryptedExportedRoomKeys = OlmMachine.encryptExportedRoomKeys(
exportedRoomKeys,
encryptionPassphrase,
100_000,
);
expect(encryptedExportedRoomKeys).toMatch(/^-----BEGIN MEGOLM SESSION DATA-----/);
});
test("can decrypt the exported room keys", () => {
const decryptedExportedRoomKeys = OlmMachine.decryptExportedRoomKeys(
encryptedExportedRoomKeys,
encryptionPassphrase,
);
expect(decryptedExportedRoomKeys).toStrictEqual(exportedRoomKeys);
});
test("can import room keys", async () => {
const progressListener = (progress, total) => {
expect(progress).toBeLessThan(total);
// Since it's called only once, let's be crazy.
expect(progress).toStrictEqual(0n);
expect(total).toStrictEqual(1n);
};
let m = await machine();
const result = JSON.parse(await m.importRoomKeys(exportedRoomKeys, progressListener));
expect(result).toMatchObject({
imported_count: expect.any(Number),
total_count: expect.any(Number),
keys: expect.any(Object),
});
});
test("importing room keys calls RoomKeyUpdatedCallback", async () => {
const callback = jest.fn();
callback.mockImplementation(() => Promise.resolve(undefined));
let m = await machine();
m.registerRoomKeyUpdatedCallback(callback);
await m.importRoomKeys(exportedRoomKeys, (_, _1) => {});
expect(callback).toHaveBeenCalledTimes(1);
let keyInfoList = callback.mock.calls[0][0];
expect(keyInfoList.length).toEqual(1);
expect(keyInfoList[0].roomId.toString()).toStrictEqual(room.toString());
});
});
describe("can do in-room verification", () => {
let m;
const user = new UserId("@alice:example.org");
const device = new DeviceId("JLAFKJWSCS");
const room = new RoomId("!test:localhost");
beforeAll(async () => {
m = await machine(user, device);
});
test("can inject devices from someone else", async () => {
{
const hypothetical_response = JSON.stringify({
device_keys: {
"@example:morpheus.localhost": {
ATRLDCRXAC: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "ATRLDCRXAC",
keys: {
"curve25519:ATRLDCRXAC": "cAVT5Es3Z3F5pFD+2w3HT7O9+R3PstzYVkzD51X/FWQ",
"ed25519:ATRLDCRXAC": "V2w/T/x7i7AXiCCtS6JldrpbvRliRoef3CqTUNqMRHA",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ATRLDCRXAC":
"ro2BjO5J6089B/JOANHnFmGrogrC2TIdMlgJbJO00DjOOcGxXfvOezCFIORTwZNHvkHU617YIGl/4keTDIWvBQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "Element Desktop: Linux",
},
},
EYYGYTCTNC: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "EYYGYTCTNC",
keys: {
"curve25519:EYYGYTCTNC": "Pqu50fo472wgb6NjKkaUxjuqoAIEAmhln2gw/zSQ7Ek",
"ed25519:EYYGYTCTNC": "Pf/2QPvui8lDty6TCTglVPRVM+irNHYavNNkyv5yFpU",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:EYYGYTCTNC":
"pnP5BYLEUUaxDgrvdzCznkjNDbvY1/MFBr1JejdnLiXlcmxRULQpIWZUCO7QTbULsCwMsYQNGn50nfmjBQX3CQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "WeeChat-Matrix-rs",
},
},
SUMODVLSIU: {
algorithms: ["m.olm.v1.curve25519-aes-sha2", "m.megolm.v1.aes-sha2"],
device_id: "SUMODVLSIU",
keys: {
"curve25519:SUMODVLSIU": "geQXWGWc++gcUHk0JcFmEVSjyzDOnk2mjVsUQwbNqQU",
"ed25519:SUMODVLSIU": "ccktaQ3g+B18E6FwVhTBYie26OlHbvDUzDEtxOQ4Qcs",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:SUMODVLSIU":
"Yn+AOxHRt1GQpY2xT2Jcqqn8jh5+Vw23ctA7NXyDiWPsLPLNTpjGWHMjZdpUqflQvpiKfhODPICoIa7Pu0iSAg",
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
"Cio6k/sq289XNTOvTCWre7Q6zg+A3euzMUe7Uy1T3gPqYFzX+kt7EAxrhbPqx1HyXAEz9zD0D/uw9VEXFCvWBQ",
},
},
user_id: "@example:morpheus.localhost",
unsigned: {
device_display_name: "Element Desktop (Linux)",
},
},
},
},
failures: {},
master_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["master"],
keys: {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:SUMODVLSIU":
"RL6WOuuzB/mZ+edfUFG/KeEcmKh+NaWpM6m2bUYmDnJrtTCYyoU+pgHJuL2/6nynemmONo18JEHBuqtNcMq2AQ",
},
},
},
},
self_signing_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["self_signing"],
keys: {
"ed25519:rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM":
"rUiMNDjIu6gqsrhJPbj3phyIzuEtuQGrLOEa9mCbtTM",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"uCBn9rpeg6umY8H97ejN26UMp6QDwNL98869t1DoVGL50J8adLN05OZd8lYk9QzwTr2d56ZTGYSYX8kv28SDDA",
},
},
},
},
user_signing_keys: {
"@example:morpheus.localhost": {
user_id: "@example:morpheus.localhost",
usage: ["user_signing"],
keys: {
"ed25519:GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw":
"GLhEKLQ50jnF6IMEPsO2ucpHUNIUEnbBXs5gYbHg4Aw",
},
signatures: {
"@example:morpheus.localhost": {
"ed25519:ZzU4WCyBfOFitdGmfKCq6F39iQCDk/zhNNTsi+tWH7A":
"4fIyWlVzuz1pgoegNLZASycORXqKycVS0dNq5vmmwsVEudp1yrPhndnaIJ3fjF8LDHvwzXTvohOid7DiU1j0AA",
},
},
},
},
});
const marked = await m.markRequestAsSent("foo", RequestType.KeysQuery, hypothetical_response);
}
});
test("can start an in-room SAS verification", async () => {
let _ = m.bootstrapCrossSigning(true);
const identity = await m.getIdentity(new UserId("@example:morpheus.localhost"));
expect(identity).toBeInstanceOf(UserIdentity);
expect(identity.isVerified()).toStrictEqual(false);
const eventId = new EventId("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg");
const verificationRequest = await identity.requestVerification(room, eventId);
expect(verificationRequest).toBeInstanceOf(VerificationRequest);
await m.receiveVerificationEvent(
JSON.stringify({
sender: "@example:morpheus.localhost",
type: "m.key.verification.ready",
event_id: "$QguWmaeMt6Hao7Ea6XHDInvr8ndknev79t9a2eBxlz0",
origin_server_ts: 1674037263075,
content: {
"methods": ["m.sas.v1", "m.qr_code.show.v1", "m.reciprocate.v1"],
"from_device": "SUMODVLSIU",
"m.relates_to": {
rel_type: "m.reference",
event_id: eventId.toString(),
},
},
}),
room,
);
expect(verificationRequest.roomId.toString()).toStrictEqual(room.toString());
const [_sas, outgoingVerificationRequest] = await verificationRequest.startSas();
expect(outgoingVerificationRequest).toBeInstanceOf(RoomMessageRequest);
expect(outgoingVerificationRequest.id).toBeDefined();
expect(outgoingVerificationRequest.room_id).toStrictEqual(room.toString());
expect(outgoingVerificationRequest.txn_id).toBeDefined();
expect(outgoingVerificationRequest.event_type).toStrictEqual("m.key.verification.start");
expect(outgoingVerificationRequest.body).toBeDefined();
const body = JSON.parse(outgoingVerificationRequest.body);
expect(body).toMatchObject({
"from_device": expect.any(String),
"method": "m.sas.v1",
"key_agreement_protocols": [expect.any(String)],
"hashes": [expect.any(String)],
"message_authentication_codes": [expect.any(String), expect.any(String), expect.any(String)],
"short_authentication_string": ["decimal", "emoji"],
"m.relates_to": {
rel_type: "m.reference",
event_id: eventId.toString(),
},
});
});
});
});
@@ -1,22 +0,0 @@
const {
RequestType,
KeysUploadRequest,
KeysQueryRequest,
KeysClaimRequest,
ToDeviceRequest,
SignatureUploadRequest,
RoomMessageRequest,
KeysBackupRequest,
} = require("../pkg/matrix_sdk_crypto_js");
describe("RequestType", () => {
test("has the correct variant values", () => {
expect(RequestType.KeysUpload).toStrictEqual(0);
expect(RequestType.KeysQuery).toStrictEqual(1);
expect(RequestType.KeysClaim).toStrictEqual(2);
expect(RequestType.ToDevice).toStrictEqual(3);
expect(RequestType.SignatureUpload).toStrictEqual(4);
expect(RequestType.RoomMessage).toStrictEqual(5);
expect(RequestType.KeysBackup).toStrictEqual(6);
});
});
@@ -1,31 +0,0 @@
const { DeviceLists, UserId } = require("../pkg/matrix_sdk_crypto_js");
describe(DeviceLists.name, () => {
test("can be empty", () => {
const empty = new DeviceLists();
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test("can be coerced empty", () => {
const empty = new DeviceLists([], []);
expect(empty.isEmpty()).toStrictEqual(true);
expect(empty.changed).toHaveLength(0);
expect(empty.left).toHaveLength(0);
});
test("returns the correct `changed` and `left`", () => {
const list = new DeviceLists([new UserId("@foo:bar.org")], [new UserId("@baz:qux.org")]);
expect(list.isEmpty()).toStrictEqual(false);
expect(list.changed).toHaveLength(1);
expect(list.changed[0].toString()).toStrictEqual("@foo:bar.org");
expect(list.left).toHaveLength(1);
expect(list.left[0].toString()).toStrictEqual("@baz:qux.org");
});
});
@@ -1,91 +0,0 @@
const { Tracing, LoggerLevel, OlmMachine, UserId, DeviceId } = require("../pkg/matrix_sdk_crypto_js");
describe("LoggerLevel", () => {
test("has the correct variant values", () => {
expect(LoggerLevel.Trace).toStrictEqual(0);
expect(LoggerLevel.Debug).toStrictEqual(1);
expect(LoggerLevel.Info).toStrictEqual(2);
expect(LoggerLevel.Warn).toStrictEqual(3);
expect(LoggerLevel.Error).toStrictEqual(4);
});
});
describe(Tracing.name, () => {
if (Tracing.isAvailable()) {
let tracing = new Tracing(LoggerLevel.Debug);
test("can installed several times", () => {
new Tracing(LoggerLevel.Debug);
new Tracing(LoggerLevel.Warn);
new Tracing(LoggerLevel.Debug);
});
const originalConsoleDebug = console.debug;
for (const [testName, testPreState, testPostState, expectedGotcha] of [
["can log something", () => {}, () => {}, true],
[
"can change the logger level",
() => {
tracing.minLevel = LoggerLevel.Warn;
},
() => {
tracing.minLevel = LoggerLevel.Debug;
},
false,
],
[
"can be turned off",
() => {
tracing.turnOff();
},
() => {},
false,
],
[
"can be turned on",
() => {
tracing.turnOn();
},
() => {},
true,
],
// This one *must* be the last. We are turning tracing off
// again for the other tests.
[
"can be turned off",
() => {
tracing.turnOff();
},
() => {},
false,
],
]) {
test(testName, async () => {
testPreState();
let gotcha = false;
console.debug = (msg) => {
gotcha = true;
expect(msg).not.toHaveLength(0);
};
// Do something that emits a `DEBUG` log.
await OlmMachine.initialize(new UserId("@alice:example.org"), new DeviceId("foo"));
console.debug = originalConsoleDebug;
testPostState();
expect(gotcha).toStrictEqual(expectedGotcha);
});
}
} else {
test("cannot be constructed", () => {
expect(() => {
new Tracing(LoggerLevel.Error);
}).toThrow();
});
}
});
@@ -1,10 +0,0 @@
{
"compilerOptions": {
"strict": true
},
"typedocOptions": {
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
"out": "docs",
"readme": "README.md"
}
}
File diff suppressed because it is too large Load Diff
@@ -1,7 +0,0 @@
/node_modules
/package-lock.json
/index.js
/index.d.ts
/matrix-sdk-crypto.*.node
/docs/*
*.tgz
@@ -1,7 +0,0 @@
src/
tests/
Cargo.toml
build.rs
*.node
*.tgz
tsconfig.json
@@ -1 +0,0 @@
/pkg
@@ -1,9 +0,0 @@
// prettier configuration: the same as the conventions used throughout Matrix.org
// see: https://github.com/matrix-org/eslint-plugin-matrix-org/blob/main/.prettierrc.js
module.exports = {
printWidth: 120,
tabWidth: 4,
quoteProps: "consistent",
trailingComma: "all",
};
@@ -1,32 +0,0 @@
# Matrix-Rust-SDK Node.js Bindings
## 0.1.0-beta.1 - 2022-07-14
- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842)
## 0.1.0-beta.0 - 2022-07-12
Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a
Node.js binding for the Rust `matrix-sdk-crypto` library. This is a
no-network-IO implementation of a state machine, named `OlmMachine`,
that handles E2EE (End-to-End Encryption) for Matrix clients.
The goal of this binding is _not_ to cover the entirety of the
`matrix-sdk-crypto` API, but only what's required to build Matrix bots
or Matrix bridges (i.e. to connect different networks together via the
Matrix protocol).
This project replaces and deprecates a previous project, with the same
name and same goals, inside [the `matrix-rust-sdk-bindings`
repository](https://github.com/matrix-org/matrix-rust-sdk-bindings),
with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The
The new official package name is
`@matrix-org/matrix-sdk-crypto-nodejs`.
Note: All bindings are now part of [the `matrix-rust-sdk`
repository](https://github.com/matrix-org/matrix-rust-sdk) (see the
`bindings/` root directory).
[A documentation is available inside the new
`matrix-sdk-crypto-nodejs`
project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs).
@@ -1,45 +0,0 @@
[package]
authors = ["Ivan Enderlin <ivane@element.io>"]
description = "Matrix encryption library, for NodeJS"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ruma", "nio"]
license = "Apache-2.0"
name = "matrix-sdk-crypto-nodejs"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = { workspace = true }
version = "0.6.0"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
[lib]
crate-type = ["cdylib"]
[features]
default = ["bundled-sqlite"]
bundled-sqlite = ["matrix-sdk-sqlite/bundled"]
qrcode = ["matrix-sdk-crypto/qrcode"]
tracing = ["dep:tracing-subscriber"]
[dependencies]
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-sqlite = { version = "0.1.0", path = "../../crates/matrix-sdk-sqlite", features = ["crypto-store"] }
ruma = { workspace = true, features = ["rand"] }
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
napi-derive = "2.9.1"
serde_json = { workspace = true }
http = { workspace = true }
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
vodozemac = { workspace = true, features = ["js"] }
zeroize = { workspace = true }
[dependencies.matrix-sdk-crypto]
path = "../../crates/matrix-sdk-crypto"
version = "0.6.0"
default_features = false
features = ["js", "automatic-room-key-forwarding"]
[build-dependencies]
napi-build = "2.0.0"
-220
View File
@@ -1,220 +0,0 @@
# `matrix-sdk-crypto-nodejs`
Welcome to the [Node.js] binding for the Rust [`matrix-sdk-crypto`]
library! This binding is part of the [`matrix-rust-sdk`] project,
which is a library implementation of a [Matrix] client-server.
`matrix-sdk-crypto-nodejs` is a no-network-IO implementation of a
state machine, named `OlmMachine`, that handles E2EE ([End-to-End
Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
[Matrix] clients.
## Usage
Just add the latest release to your `package.json`:
```sh
$ npm install --save @matrix-org/matrix-sdk-crypto-nodejs
```
When installing, NPM will download the corresponding prebuilt Rust library for your current host system. The following are supported:
<table>
<thead>
<tr>
<th>Platform</th>
<th>Architecture</th>
<th>Triple</th>
<th>Prebuilt available</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5">Linux</td>
<td rowspan="2"><code>aarch</code></td>
<td><code>aarch64-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>arm-unknown-linux-gnueabihf</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="3"><code>amd</code></td>
<td><code>x86_64-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>x86_64-unknown-linux-musl</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>i686-unknown-linux-gnu</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="2">macOS</td>
<td><code>aarch</code></td>
<td><code>arch64-apple-darwin</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>amd</code></td>
<td><code>x86_64-apple-darwin</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="3">Windows</td>
<td><code>aarch</code></td>
<td><code>aarch64-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
<tr>
<td rowspan="2"><code>amd</code></td>
<td><code>x86_64-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
<tr>
<td><code>i686-pc-windows-msvc</code></td>
<td>✅</td>
</tr>
</tbody>
</table>
## Development
This Node.js binding is written in [Rust]. To build this binding, you
need to install the Rust compiler, see [the Install Rust
Page](https://www.rust-lang.org/tools/install). Then, the workflow is
pretty classical by using [npm], see [the Downloading and installing
Node.js and npm
Page](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
The binding is compatible with, and tested against, the Node.js
versions that are in “current”, “active” or “maintenance” states,
according to [the Node.js Releases
Page](https://nodejs.org/en/about/releases/), _and_ which are
compatible with [NAPI v6 (Node.js
API)](https://nodejs.org/api/n-api.html#node-api-version-matrix). It
means that this binding will work with the following versions: 16.0.0,
18.0.0, 19.0.0 and 20.0.0.
Once the Rust compiler, Node.js and npm are installed, you can run the
following commands:
```sh
$ npm install --ignore-scripts
$ npm run build
$ npm run test
```
An `index.js`, `index.d.ts` and a `*.node` files should be
generated. At the same level of those files, you can edit a file and
try this:
```javascript
const { OlmMachine } = require("./index.js");
// Let's see what we can do.
```
The `OlmMachine` state machine works in a push/pull manner:
- You push state changes and events retrieved from a Matrix homeserver
`/sync` response, into the state machine,
- You pull requests that you will need to send back to the homeserver
out of the state machine.
```javascript
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists } = require("./index.js");
async function main() {
// Define a user ID.
const alice = new UserId("@alice:example.org");
// Define a device ID.
const device = new DeviceId("DEVICEID");
// Let's create the `OlmMachine` state machine.
const machine = await OlmMachine.initialize(alice, device);
// Let's pretend we have received changes and events from a
// `/sync` endpoint of a Matrix homeserver, …
const toDeviceEvents = "[]"; // JSON-encoded list of events
const changedDevices = new DeviceLists();
const oneTimeKeyCounts = {};
const unusedFallbackKeys = [];
// … and push them into the state machine.
const decryptedToDevice = await machine.receiveSyncChanges(
toDeviceEvents,
changedDevices,
oneTimeKeyCounts,
unusedFallbackKeys,
);
// Now, let's pull requests that we need to send out to the Matrix
// homeserver.
const outgoingRequests = await machine.outgoingRequests();
// To complete the workflow, send the requests here out and call
// `machine.markRequestAsSent`.
}
main();
```
### With tracing (experimental)
If you want to enable [tracing](https://tracing.rs), i.e. to get the
logs, you should re-compile the extension with the `tracing` feature
turned on:
```sh
$ npm run build -- --features tracing
```
Now, you can use the `MATRIX_LOG` environment variable to tweak the log filtering, such as:
```sh
$ MATRIX_LOG=debug npm run test
```
See
[`tracing-subscriber`](https://tracing.rs/tracing_subscriber/index.html)
to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
#### Using tracing in dependent projects
To enable tracing in client applications that import these bindings, here's how to do it in
a local development environment:
- In this directory, run `npm link` to make your local build of the bindings available to
other Node projects on your system
- In your client app's source directory, run `npm link @matrix-org/matrix-sdk-crypto-nodejs`
to make it use your trace-enabled local build of the bindings
- In your client app's source code, add a call to `initTracing` near startup time
- Run your app with the `MATRIX_LOG` environment variable set to the desired log level
Either `npm link` command may be substituted with `yarn link`.
## Documentation
[The documentation can be found
online](https://matrix-org.github.io/matrix-rust-sdk/bindings/matrix-sdk-crypto-nodejs/).
To generate the documentation locally, please run the following
command:
```sh
$ npm run doc
```
The documentation is generated in the `./docs` directory.
[Node.js]: https://nodejs.org/
[`matrix-sdk-crypto`]: https://github.com/matrix-org/matrix-rust-sdk/tree/main/crates/matrix-sdk-crypto
[`matrix-rust-sdk`]: https://github.com/matrix-org/matrix-rust-sdk
[Matrix]: https://matrix.org/
[Rust]: https://www.rust-lang.org/
[npm]: https://www.npmjs.com/
@@ -1,3 +0,0 @@
fn main() {
napi_build::setup();
}
@@ -1,121 +0,0 @@
const { HttpsProxyAgent } = require("https-proxy-agent");
const { DownloaderHelper } = require("node-downloader-helper");
const { version } = require("./package.json");
const { platform, arch } = process;
const DOWNLOADS_BASE_URL = "https://github.com/matrix-org/matrix-rust-sdk/releases/download";
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
const byteHelper = function (value) {
if (value === 0) {
return "0 b";
}
const units = ["b", "kB", "MB", "GB", "TB"];
const number = Math.floor(Math.log(value) / Math.log(1024));
return (value / Math.pow(1024, Math.floor(number))).toFixed(1) + " " + units[number];
};
function download_lib(libname) {
let startTime = new Date();
const url = `${DOWNLOADS_BASE_URL}/${CURRENT_VERSION}/${libname}`;
console.info(`Downloading lib ${libname} from ${url}`);
const dl = new DownloaderHelper(url, __dirname, {
override: true,
});
const proxy = process.env.https_proxy ?? process.env.HTTPS_PROXY;
if (proxy) {
const proxyAgent = new HttpsProxyAgent(proxy);
dl.updateOptions({
httpsRequestOptions: { agent: proxyAgent },
});
}
dl.on("end", () => console.info("Download Completed"));
dl.on("error", (err) => console.info("Download Failed", err));
dl.on("progress", (stats) => {
const progress = stats.progress.toFixed(1);
const speed = byteHelper(stats.speed);
const downloaded = byteHelper(stats.downloaded);
const total = byteHelper(stats.total);
// print every one second (`progress.throttled` can be used instead)
const currentTime = new Date();
const elaspsedTime = currentTime - startTime;
if (elaspsedTime > 1000) {
startTime = currentTime;
console.info(`${speed}/s - ${progress}% [${downloaded}/${total}]`);
}
});
dl.start().catch((err) => console.error(err));
}
function isMusl() {
// For Node 10
if (!process.report || typeof process.report.getReport !== "function") {
try {
return readFileSync("/usr/bin/ldd", "utf8").includes("musl");
} catch (e) {
return true;
}
} else {
const { glibcVersionRuntime } = process.report.getReport().header;
return !glibcVersionRuntime;
}
}
switch (platform) {
case "win32":
switch (arch) {
case "x64":
download_lib("matrix-sdk-crypto.win32-x64-msvc.node");
break;
case "ia32":
download_lib("matrix-sdk-crypto.win32-ia32-msvc.node");
break;
case "arm64":
download_lib("matrix-sdk-crypto.win32-arm64-msvc.node");
break;
default:
throw new Error(`Unsupported architecture on Windows: ${arch}`);
}
break;
case "darwin":
switch (arch) {
case "x64":
download_lib("matrix-sdk-crypto.darwin-x64.node");
break;
case "arm64":
download_lib("matrix-sdk-crypto.darwin-arm64.node");
break;
default:
throw new Error(`Unsupported architecture on macOS: ${arch}`);
}
break;
case "linux":
switch (arch) {
case "x64":
if (isMusl()) {
download_lib("matrix-sdk-crypto.linux-x64-musl.node");
} else {
download_lib("matrix-sdk-crypto.linux-x64-gnu.node");
}
break;
case "arm64":
if (isMusl()) {
throw new Error("Linux for arm64 musl isn't support at the moment");
} else {
download_lib("matrix-sdk-crypto.linux-arm64-gnu.node");
}
break;
case "arm":
download_lib("matrix-sdk-crypto.linux-arm-gnueabihf.node");
break;
default:
throw new Error(`Unsupported architecture on Linux: ${arch}`);
}
break;
default:
throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`);
}
@@ -1,37 +0,0 @@
{
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
"version": "0.1.0-beta.1",
"main": "index.js",
"types": "index.d.ts",
"napi": {
"name": "matrix-sdk-crypto",
"triples": {
"additional": [
"aarch64-apple-darwin"
]
}
},
"license": "Apache-2.0",
"devDependencies": {
"@napi-rs/cli": "^2.9.0",
"jest": "^28.1.0",
"prettier": "^2.8.3",
"typedoc": "^0.22.17",
"yargs-parser": "~21.0.1"
},
"engines": {
"node": ">= 16"
},
"scripts": {
"lint": "prettier --check .",
"release-build": "napi build --platform --release --strip",
"build": "napi build --platform",
"postinstall": "node download-lib.js",
"test": "jest --verbose --testTimeout 10000",
"doc": "typedoc --tsconfig ."
},
"dependencies": {
"https-proxy-agent": "^5.0.1",
"node-downloader-helper": "^2.1.5"
}
}
@@ -1,123 +0,0 @@
use std::{
io::{Cursor, Read},
ops::Deref,
};
use napi::bindgen_prelude::Uint8Array;
use napi_derive::*;
use crate::into_err;
/// A type to encrypt and to decrypt anything that can fit in an
/// `Uint8Array`, usually big buffer.
#[napi]
pub struct Attachment;
#[napi]
impl Attachment {
/// Encrypt the content of the `Uint8Array`.
///
/// It produces an `EncryptedAttachment`, which can be used to
/// retrieve the media encryption information, or the encrypted
/// data.
#[napi]
pub fn encrypt(array: Uint8Array) -> napi::Result<EncryptedAttachment> {
let buffer: &[u8] = array.deref();
let mut cursor = Cursor::new(buffer);
let mut encryptor = matrix_sdk_crypto::AttachmentEncryptor::new(&mut cursor);
let mut encrypted_data = Vec::new();
encryptor.read_to_end(&mut encrypted_data).map_err(into_err)?;
let media_encryption_info = Some(encryptor.finish());
Ok(EncryptedAttachment {
encrypted_data: Uint8Array::new(encrypted_data),
media_encryption_info,
})
}
/// Decrypt an `EncryptedAttachment`.
///
/// The encrypted attachment can be created manually, or from the
/// `encrypt` method.
///
/// **Warning**: The encrypted attachment can be used only
/// **once**! The encrypted data will still be present, but the
/// media encryption info (which contain secrets) will be
/// destroyed. It is still possible to get a JSON-encoded backup
/// by calling `EncryptedAttachment.mediaEncryptionInfo`.
#[napi]
pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result<Uint8Array> {
let Some(media_encryption_info) = attachment.media_encryption_info.take() else {
return Err(napi::Error::from_reason(
"The media encryption info are absent from the given encrypted attachment"
.to_owned(),
));
};
let encrypted_data: &[u8] = attachment.encrypted_data.deref();
let mut cursor = Cursor::new(encrypted_data);
let mut decryptor =
matrix_sdk_crypto::AttachmentDecryptor::new(&mut cursor, media_encryption_info)
.map_err(into_err)?;
let mut decrypted_data = Vec::new();
decryptor.read_to_end(&mut decrypted_data).map_err(into_err)?;
Ok(Uint8Array::new(decrypted_data))
}
}
/// An encrypted attachment, usually created from `Attachment.encrypt`.
#[napi]
pub struct EncryptedAttachment {
media_encryption_info: Option<matrix_sdk_crypto::MediaEncryptionInfo>,
/// The actual encrypted data.
pub encrypted_data: Uint8Array,
}
#[napi]
impl EncryptedAttachment {
/// Create a new encrypted attachment manually.
///
/// It needs encrypted data, stored in an `Uint8Array`, and a
/// [media encryption
/// information](https://docs.rs/matrix-sdk-crypto/latest/matrix_sdk_crypto/struct.MediaEncryptionInfo.html),
/// as a JSON-encoded string.
///
/// The media encryption information aren't stored as a string:
/// they are parsed, validated and fully deserialized.
///
/// See [the specification to learn
/// more](https://spec.matrix.org/unstable/client-server-api/#extensions-to-mroommessage-msgtypes).
#[napi(constructor)]
pub fn new(encrypted_data: Uint8Array, media_encryption_info: String) -> napi::Result<Self> {
Ok(Self {
encrypted_data,
media_encryption_info: Some(
serde_json::from_str(media_encryption_info.as_str()).map_err(into_err)?,
),
})
}
/// Return the media encryption info as a JSON-encoded string. The
/// structure is fully valid.
///
/// If the media encryption info have been consumed already, it
/// will return `null`.
#[napi(getter)]
pub fn media_encryption_info(&self) -> Option<String> {
serde_json::to_string(self.media_encryption_info.as_ref()?).ok()
}
/// Check whether the media encryption info has been consumed by
/// `Attachment.decrypt` already.
#[napi(getter)]
pub fn has_media_encryption_info_been_consumed(&self) -> bool {
self.media_encryption_info.is_none()
}
}
@@ -1,140 +0,0 @@
use std::time::Duration;
use matrix_sdk_common::deserialized_responses::ShieldState as RustShieldState;
use napi::bindgen_prelude::{BigInt, FromNapiValue, ToNapiValue};
use napi_derive::*;
use crate::events;
/// An encryption algorithm to be used to encrypt messages sent to a
/// room.
#[napi]
pub enum EncryptionAlgorithm {
/// Olm version 1 using Curve25519, AES-256, and SHA-256.
OlmV1Curve25519AesSha2,
/// Megolm version 1 using AES-256 and SHA-256.
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
}
}
}
impl From<matrix_sdk_crypto::types::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: matrix_sdk_crypto::types::EventEncryptionAlgorithm) -> Self {
use matrix_sdk_crypto::types::EventEncryptionAlgorithm::*;
match value {
OlmV1Curve25519AesSha2 => Self::OlmV1Curve25519AesSha2,
MegolmV1AesSha2 => Self::MegolmV1AesSha2,
_ => unreachable!("Unknown variant"),
}
}
}
/// Settings for an encrypted room.
///
/// This determines the algorithm and rotation periods of a group
/// session.
#[napi]
pub struct EncryptionSettings {
/// The encryption algorithm that should be used in the room.
pub algorithm: EncryptionAlgorithm,
/// How long the session should be used before changing it,
/// expressed in microseconds.
pub rotation_period: BigInt,
/// How many messages should be sent before changing the session.
pub rotation_period_messages: BigInt,
/// The history visibility of the room when the session was
/// created.
pub history_visibility: events::HistoryVisibility,
/// Should untrusted devices receive the room key, or should they be
/// excluded from the conversation.
pub only_allow_trusted_devices: bool,
}
impl Default for EncryptionSettings {
fn default() -> Self {
let default = matrix_sdk_crypto::olm::EncryptionSettings::default();
Self {
algorithm: default.algorithm.into(),
rotation_period: {
let n: u64 = default.rotation_period.as_micros().try_into().unwrap();
n.into()
},
rotation_period_messages: {
let n = default.rotation_period_msgs;
n.into()
},
history_visibility: default.history_visibility.into(),
only_allow_trusted_devices: default.only_allow_trusted_devices,
}
}
}
#[napi]
impl EncryptionSettings {
/// Create a new `EncryptionSettings` with default values.
#[napi(constructor)]
pub fn new() -> EncryptionSettings {
Self::default()
}
}
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
fn from(value: &EncryptionSettings) -> Self {
Self {
algorithm: value.algorithm.into(),
rotation_period: Duration::from_micros(value.rotation_period.get_u64().1),
rotation_period_msgs: value.rotation_period_messages.get_u64().1,
history_visibility: value.history_visibility.into(),
only_allow_trusted_devices: value.only_allow_trusted_devices,
}
}
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[napi]
pub enum ShieldColor {
Red,
Grey,
None,
}
/// Take a look at [`matrix_sdk_common::deserialized_responses::ShieldState`]
/// for more info.
#[napi]
pub struct ShieldState {
pub color: ShieldColor,
pub message: Option<&'static str>,
}
impl From<RustShieldState> for ShieldState {
fn from(value: RustShieldState) -> Self {
match value {
RustShieldState::Red { message } => {
ShieldState { color: ShieldColor::Red, message: Some(message) }
}
RustShieldState::Grey { message } => {
ShieldState { color: ShieldColor::Grey, message: Some(message) }
}
RustShieldState::None => ShieldState { color: ShieldColor::None, message: None },
}
}
}
@@ -1,28 +0,0 @@
/// Generic error wrapping `napi::Error`.
#[derive(Debug)]
pub struct Error(napi::Error);
impl<E> From<E> for Error
where
E: std::error::Error,
{
fn from(error: E) -> Self {
Self(napi::Error::from_reason(error.to_string()))
}
}
impl From<Error> for napi::Error {
#[inline]
fn from(value: Error) -> Self {
value.0
}
}
/// Helper to replace the `E` to `Error` to `napi::Error` conversion.
#[inline]
pub fn into_err<E>(error: E) -> napi::Error
where
E: std::error::Error,
{
Error::from(error).into()
}
@@ -1,62 +0,0 @@
//! Types related to events.
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::events::room::history_visibility::HistoryVisibility as RumaHistoryVisibility;
/// Who can see a room's history.
#[napi]
#[derive(Debug)]
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's 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's 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,
}
impl From<HistoryVisibility> for RumaHistoryVisibility {
fn from(value: HistoryVisibility) -> Self {
use HistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
}
}
}
impl From<RumaHistoryVisibility> for HistoryVisibility {
fn from(value: RumaHistoryVisibility) -> Self {
use RumaHistoryVisibility::*;
match value {
Invited => Self::Invited,
Joined => Self::Joined,
Shared => Self::Shared,
WorldReadable => Self::WorldReadable,
_ => unreachable!("Unknown variant"),
}
}
}
@@ -1,281 +0,0 @@
//! Types for [Matrix](https://matrix.org/) identifiers for devices,
//! events, keys, rooms, servers, users and URIs.
use napi::bindgen_prelude::{FromNapiValue, ToNapiValue};
use napi_derive::*;
use crate::into_err;
/// A Matrix [user ID].
///
/// [user ID]: https://spec.matrix.org/v1.2/appendices/#user-identifiers
#[napi]
#[derive(Debug, Clone)]
pub struct UserId {
pub(crate) inner: ruma::OwnedUserId,
}
impl From<ruma::OwnedUserId> for UserId {
fn from(inner: ruma::OwnedUserId) -> Self {
Self { inner }
}
}
#[napi]
impl UserId {
/// Parse/validate and create a new `UserId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::UserId::parse(id.as_str()).map_err(into_err)?))
}
/// Returns the user's localpart.
#[napi(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the user ID.
#[napi(getter)]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Whether this user ID is a historical one.
///
/// A historical user ID is one that doesn't conform to the latest
/// specification of the user ID grammar but is still accepted
/// because it was previously allowed.
#[napi]
pub fn is_historical(&self) -> bool {
self.inner.is_historical()
}
/// Return the user ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix device ID.
///
/// Device identifiers in Matrix are completely opaque character
/// sequences. This type is provided simply for its semantic value.
#[napi]
#[derive(Debug, Clone)]
pub struct DeviceId {
pub(crate) inner: ruma::OwnedDeviceId,
}
impl From<ruma::OwnedDeviceId> for DeviceId {
fn from(inner: ruma::OwnedDeviceId) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceId {
/// Create a new `DeviceId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> Self {
Self::from(Into::<ruma::OwnedDeviceId>::into(id))
}
/// Return the device ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix device key ID.
///
/// A key algorithm and a device ID, combined with a :.
#[napi]
#[derive(Debug, Clone)]
pub struct DeviceKeyId {
pub(crate) inner: ruma::OwnedDeviceKeyId,
}
impl From<ruma::OwnedDeviceKeyId> for DeviceKeyId {
fn from(inner: ruma::OwnedDeviceKeyId) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceKeyId {
/// Parse/validate and create a new `DeviceKeyId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::DeviceKeyId::parse(id.as_str()).map_err(into_err)?))
}
/// Returns key algorithm of the device key ID.
#[napi(getter)]
pub fn algorithm(&self) -> DeviceKeyAlgorithm {
self.inner.algorithm().into()
}
/// Returns device ID of the device key ID.
#[napi(getter)]
pub fn device_id(&self) -> DeviceId {
self.inner.device_id().to_owned().into()
}
/// Return the device key ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithms in the specification.
#[napi]
pub struct DeviceKeyAlgorithm {
inner: ruma::DeviceKeyAlgorithm,
}
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithm {
fn from(inner: ruma::DeviceKeyAlgorithm) -> Self {
Self { inner }
}
}
#[napi]
impl DeviceKeyAlgorithm {
/// Read the device key algorithm's name. If the name is
/// `Unknown`, one may be interested by the `to_string` method to
/// read the original name.
#[napi(getter)]
pub fn name(&self) -> DeviceKeyAlgorithmName {
self.inner.clone().into()
}
/// Return the device key algorithm as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.to_string()
}
}
/// The basic key algorithm names in the specification.
#[napi]
pub enum DeviceKeyAlgorithmName {
/// The Ed25519 signature algorithm.
Ed25519,
/// The Curve25519 ECDH algorithm.
Curve25519,
/// The Curve25519 ECDH algorithm, but the key also contains
/// signatures.
SignedCurve25519,
/// An unknown device key algorithm.
Unknown,
}
impl From<ruma::DeviceKeyAlgorithm> for DeviceKeyAlgorithmName {
fn from(value: ruma::DeviceKeyAlgorithm) -> Self {
use ruma::DeviceKeyAlgorithm::*;
match value {
Ed25519 => Self::Ed25519,
Curve25519 => Self::Curve25519,
SignedCurve25519 => Self::SignedCurve25519,
_ => Self::Unknown,
}
}
}
/// A Matrix [room ID].
///
/// [room ID]: https://spec.matrix.org/v1.2/appendices/#room-ids-and-event-ids
#[napi]
#[derive(Debug, Clone)]
pub struct RoomId {
pub(crate) inner: ruma::OwnedRoomId,
}
impl From<ruma::OwnedRoomId> for RoomId {
fn from(inner: ruma::OwnedRoomId) -> Self {
Self { inner }
}
}
#[napi]
impl RoomId {
/// Parse/validate and create a new `RoomId`.
#[napi(constructor, strict)]
pub fn new(id: String) -> napi::Result<Self> {
Ok(Self::from(ruma::RoomId::parse(id).map_err(into_err)?))
}
/// Returns the user's localpart.
#[napi(getter)]
pub fn localpart(&self) -> String {
self.inner.localpart().to_owned()
}
/// Returns the server name of the room ID.
#[napi(getter)]
pub fn server_name(&self) -> ServerName {
ServerName { inner: self.inner.server_name().to_owned() }
}
/// Return the room ID as a string.
#[napi]
#[allow(clippy::inherent_to_string)]
pub fn to_string(&self) -> String {
self.inner.as_str().to_owned()
}
}
/// A Matrix-spec compliant [server name].
///
/// It consists of a host and an optional port (separated by a colon if
/// present).
///
/// [server name]: https://spec.matrix.org/v1.2/appendices/#server-name
#[napi]
#[derive(Debug)]
pub struct ServerName {
inner: ruma::OwnedServerName,
}
#[napi]
impl ServerName {
/// Parse/validate and create a new `ServerName`.
#[napi(constructor, strict)]
pub fn new(name: String) -> napi::Result<Self> {
Ok(Self { inner: ruma::ServerName::parse(name).map_err(into_err)? })
}
/// Returns the host of the server name.
///
/// That is: Return the part of the server before `:<port>` or the
/// full server name if there is no port.
#[napi(getter)]
pub fn host(&self) -> String {
self.inner.host().to_owned()
}
/// Returns the port of the server name if any.
#[napi(getter)]
pub fn port(&self) -> Option<u16> {
self.inner.port()
}
/// Returns true if and only if the server name is an IPv4 or IPv6
/// address.
#[napi]
pub fn is_ip_literal(&self) -> bool {
self.inner.is_ip_literal()
}
}
@@ -1,57 +0,0 @@
// Copyright 2022 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.
#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
//#![warn(missing_docs, missing_debug_implementations)]
use napi_derive::napi;
pub mod attachment;
pub mod encryption;
mod errors;
pub mod events;
pub mod identifiers;
pub mod machine;
pub mod olm;
pub mod requests;
pub mod responses;
pub mod sync_events;
#[cfg(feature = "tracing")]
pub mod tracing;
pub mod types;
pub mod vodozemac;
/// Object containing the versions of the Rust libraries we are using.
#[napi]
pub struct Versions {
/// The version of the vodozemac crate.
#[napi(getter)]
pub vodozemac: String,
/// The version of the matrix-sdk-crypto crate.
#[napi(getter)]
pub matrix_sdk_crypto: String,
}
/// Get the versions of the Rust libraries we are using.
#[napi(js_name = "getVersions")]
pub fn get_versions() -> Versions {
Versions {
vodozemac: matrix_sdk_crypto::vodozemac::VERSION.to_owned(),
matrix_sdk_crypto: matrix_sdk_crypto::VERSION.to_owned(),
}
}
use crate::errors::into_err;
@@ -1,479 +0,0 @@
//! The crypto specific Olm objects.
use std::{
collections::{BTreeMap, HashMap},
mem::ManuallyDrop,
ops::Deref,
sync::Arc,
};
use napi::bindgen_prelude::{within_runtime_if_available, Either7, FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{value::RawValue, Value as JsonValue};
use zeroize::Zeroize;
use crate::{
encryption, identifiers, into_err, olm, requests, responses, responses::response_from_string,
sync_events, types, vodozemac,
};
/// The value used by the `OlmMachine` JS class.
///
/// It has 2 states: `Opened` and `Closed`. Why maintaining the state here?
/// Because NodeJS has no way to drop an object explicitly, and we want to be
/// able to “close” the `OlmMachine` to free all associated data. More over,
/// `napi-rs` doesn't allow a function to take the ownership of the type itself
/// (`fn close(self) { … }`). So we manage the state ourselves.
///
/// Using the `OlmMachine` when its state is `Closed` will panic.
enum OlmMachineInner {
Opened(ManuallyDrop<matrix_sdk_crypto::OlmMachine>),
Closed,
}
impl Drop for OlmMachineInner {
fn drop(&mut self) {
if let Self::Opened(machine) = self {
// SAFETY: `self` won't be used anymore after this `take`, so it's safe to do it
// here.
let machine = unsafe { ManuallyDrop::take(machine) };
within_runtime_if_available(move || drop(machine));
}
}
}
impl Deref for OlmMachineInner {
type Target = matrix_sdk_crypto::OlmMachine;
#[inline]
fn deref(&self) -> &Self::Target {
match self {
Self::Opened(machine) => machine,
Self::Closed => panic!("The `OlmMachine` has been closed, cannot use it anymore"),
}
}
}
/// Represents the type of store an `OlmMachine` can use.
#[derive(Default)]
#[napi]
pub enum StoreType {
/// Use `matrix-sdk-sqlite`.
#[default]
Sqlite,
}
/// State machine implementation of the Olm/Megolm encryption protocol
/// used for Matrix end to end encryption.
// #[napi(custom_finalize)]
#[napi]
pub struct OlmMachine {
inner: OlmMachineInner,
}
#[napi]
impl OlmMachine {
// JavaScript doesn't support asynchronous constructor. So let's
// use a factory pattern, where the constructor cannot be used (it
// returns an error), and a new method is provided to construct
// the object. napi provides `#[napi(factory)]` to address those
// needs automatically. Unfortunately, it doesn't support
// asynchronous factory methods.
//
// So let's do this manually. The `initialize` async method _is_
// the factory function. We also manually implement the
// constructor to raise an error when called.
/// Create a new `OlmMachine` asynchronously.
///
/// The persistence of the encryption keys and all the inner
/// objects are controlled by the `store_path` argument.
///
/// # Arguments
///
/// * `user_id`, the unique ID of the user that owns this machine.
/// * `device_id`, the unique id of the device that owns this machine.
/// * `store_path`, the path to a directory where the state of the machine
/// should be persisted; if not set, the created machine will keep the
/// encryption keys only in memory, and once the object is dropped, the
/// keys will be lost.
/// * `store_passphrase`, the passphrase that should be used to encrypt the
/// data at rest in the store. **Warning**, if no passphrase is given, the
/// store and all its data will remain unencrypted. This argument is
/// ignored if `store_path` is not set.
#[napi(strict)]
pub async fn initialize(
user_id: &identifiers::UserId,
device_id: &identifiers::DeviceId,
store_path: Option<String>,
mut store_passphrase: Option<String>,
store_type: Option<StoreType>,
) -> napi::Result<OlmMachine> {
let user_id = user_id.clone().inner;
let device_id = device_id.clone().inner;
let user_id = user_id.as_ref();
let device_id = device_id.as_ref();
Ok(OlmMachine {
inner: OlmMachineInner::Opened(ManuallyDrop::new(match store_path {
Some(store_path) => {
let machine = match store_type.unwrap_or_default() {
StoreType::Sqlite => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id,
device_id,
matrix_sdk_sqlite::SqliteCryptoStore::open(
store_path,
store_passphrase.as_deref(),
)
.await
.map(Arc::new)
.map_err(into_err)?,
)
.await
}
};
store_passphrase.zeroize();
machine.map_err(into_err)?
}
None => matrix_sdk_crypto::OlmMachine::new(user_id, device_id).await,
})),
})
}
/// It's not possible to construct an `OlmMachine` with its
/// constructor because building an `OlmMachine` is
/// asynchronous. Please use the `finalize` method.
#[napi(constructor)]
pub fn new() -> napi::Result<Self> {
Err(napi::Error::from_reason(
"To build an `OlmMachine`, please use the `initialize` method",
))
}
/// The unique user ID that owns this `OlmMachine` instance.
#[napi(getter)]
pub fn user_id(&self) -> identifiers::UserId {
identifiers::UserId::from(self.inner.user_id().to_owned())
}
/// The unique device ID that identifies this `OlmMachine`.
#[napi(getter)]
pub fn device_id(&self) -> identifiers::DeviceId {
identifiers::DeviceId::from(self.inner.device_id().to_owned())
}
/// Get the public parts of our Olm identity keys.
#[napi(getter)]
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
self.inner.identity_keys().into()
}
/// Handle a to-device and one-time key counts from a sync response.
///
/// This will decrypt and handle to-device events returning the
/// decrypted versions of them, as a JSON-encoded string.
///
/// To decrypt an event from the room timeline, please use
/// `decrypt_room_event`.
///
/// # Arguments
///
/// * `to_device_events`, the to-device events of the current sync response.
/// * `changed_devices`, the list of devices that changed in this sync
/// response.
/// * `one_time_keys_count`, the current one-time keys counts that the sync
/// response returned.
#[napi(strict)]
pub async fn receive_sync_changes(
&self,
to_device_events: String,
changed_devices: &sync_events::DeviceLists,
one_time_key_counts: HashMap<String, u32>,
unused_fallback_keys: Vec<String>,
) -> napi::Result<String> {
let to_device_events = serde_json::from_str(to_device_events.as_ref()).map_err(into_err)?;
let changed_devices = changed_devices.inner.clone();
let one_time_key_counts = one_time_key_counts
.iter()
.map(|(key, value)| (DeviceKeyAlgorithm::from(key.as_str()), UInt::from(*value)))
.collect::<BTreeMap<_, _>>();
let unused_fallback_keys = Some(
unused_fallback_keys
.into_iter()
.map(|key| DeviceKeyAlgorithm::from(key.as_str()))
.collect::<Vec<_>>(),
);
serde_json::to_string(
&self
.inner
.receive_sync_changes(
to_device_events,
&changed_devices,
&one_time_key_counts,
unused_fallback_keys.as_deref(),
)
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Get the outgoing requests that need to be sent out.
///
/// This returns a list of `KeysUploadRequest`, or
/// `KeysQueryRequest`, or `KeysClaimRequest`, or
/// `ToDeviceRequest`, or `SignatureUploadRequest`, or
/// `RoomMessageRequest`, or `KeysBackupRequest`. Those requests
/// need to be sent out to the server and the responses need to be
/// passed back to the state machine using `mark_request_as_sent`.
#[napi]
pub async fn outgoing_requests(
&self,
) -> napi::Result<
Vec<
// We could be tempted to use `requests::OutgoingRequests` as its
// a type alias for this giant `Either7`. But `napi` won't unfold
// it properly into a valid TypeScript definition, so… let's
// copy-paste :-(.
Either7<
requests::KeysUploadRequest,
requests::KeysQueryRequest,
requests::KeysClaimRequest,
requests::ToDeviceRequest,
requests::SignatureUploadRequest,
requests::RoomMessageRequest,
requests::KeysBackupRequest,
>,
>,
> {
self.inner
.outgoing_requests()
.await
.map_err(into_err)?
.into_iter()
.map(requests::OutgoingRequest)
.map(TryFrom::try_from)
.collect()
}
/// Mark the request with the given request ID as sent.
///
/// # Arguments
///
/// * `request_id`, the unique ID of the request that was sent out. This is
/// needed to couple the response with the now sent out request.
/// * `request_type`, the request type associated to the request ID.
/// * `response`, the response that was received from the server after the
/// outgoing request was sent out.
#[napi(strict)]
pub async fn mark_request_as_sent(
&self,
request_id: String,
request_type: requests::RequestType,
response: String,
) -> napi::Result<bool> {
let transaction_id = OwnedTransactionId::from(request_id);
let response = response_from_string(response.as_str()).map_err(into_err)?;
let incoming_response = responses::OwnedResponse::try_from((request_type, response))?;
self.inner
.mark_request_as_sent(&transaction_id, &incoming_response)
.await
.map(|_| true)
.map_err(into_err)
}
/// Get the a key claiming request for the user/device pairs that
/// we are missing Olm sessions for.
///
/// Returns `null` if no key claiming request needs to be sent
/// out.
///
/// Sessions need to be established between devices so group
/// sessions for a room can be shared with them.
///
/// This should be called every time a group session needs to be
/// shared as well as between sync calls. After a sync some
/// devices may request room keys without us having a valid Olm
/// session with them, making it impossible to server the room key
/// request, thus its necessary to check for missing sessions
/// between sync as well.
///
/// Note: Care should be taken that only one such request at a
/// time is in flight, e.g. using a lock.
///
/// The response of a successful key claiming requests needs to be
/// passed to the `OlmMachine` with the `mark_request_as_sent`.
///
/// # Arguments
///
/// * `users`, the list of users that we should check if we lack a session
/// with one of their devices. This can be an empty array or `null` when
/// calling this method between sync requests.
#[napi(strict)]
pub async fn get_missing_sessions(
&self,
users: Option<Vec<&identifiers::UserId>>,
) -> napi::Result<Option<requests::KeysClaimRequest>> {
let users = users
.unwrap_or_default()
.into_iter()
.map(|user| user.inner.clone())
.collect::<Vec<_>>();
match self
.inner
.get_missing_sessions(users.iter().map(AsRef::as_ref))
.await
.map_err(into_err)?
{
Some((transaction_id, keys_claim_request)) => Ok(Some(
requests::KeysClaimRequest::try_from((
transaction_id.to_string(),
&keys_claim_request,
))
.map_err(into_err)?,
)),
None => Ok(None),
}
}
/// Update the tracked users.
///
/// This will mark users that werent seen before for a key query
/// and tracking.
///
/// If the user is already known to the Olm machine it will not be
/// considered for a key query.
///
/// # Arguments
///
/// * `users`, an array over user IDs that should be marked for tracking.
#[napi(strict)]
pub async fn update_tracked_users(&self, users: Vec<&identifiers::UserId>) -> napi::Result<()> {
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
self.inner.update_tracked_users(users.iter().map(AsRef::as_ref)).await.map_err(into_err)?;
Ok(())
}
/// Get to-device requests to share a room key with users in a room.
///
/// # Arguments
///
/// * `room_id`, the room ID of the room where the room key will be used.
/// * `users`, the list of users that should receive the room key.
/// * `encryption_settings`, the encryption settings.
#[napi(strict)]
pub async fn share_room_key(
&self,
room_id: &identifiers::RoomId,
users: Vec<&identifiers::UserId>,
encryption_settings: &encryption::EncryptionSettings,
) -> napi::Result<String> {
let room_id = room_id.inner.clone();
let users = users.into_iter().map(|user| user.inner.clone()).collect::<Vec<_>>();
let encryption_settings =
matrix_sdk_crypto::olm::EncryptionSettings::from(encryption_settings);
serde_json::to_string(
&self
.inner
.share_room_key(&room_id, users.iter().map(AsRef::as_ref), encryption_settings)
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Encrypt a JSON-encoded content for the given room.
///
/// # Arguments
///
/// * `room_id`, the ID of the room for which the message should be
/// encrypted.
/// * `event_type`, the plaintext type of the event.
/// * `content`, the JSON-encoded content of the message that should be
/// encrypted.
#[napi(strict)]
pub async fn encrypt_room_event(
&self,
room_id: &identifiers::RoomId,
event_type: String,
content: String,
) -> napi::Result<String> {
let room_id = room_id.inner.clone();
let content: JsonValue = serde_json::from_str(content.as_str()).map_err(into_err)?;
serde_json::to_string(
&self
.inner
.encrypt_room_event_raw(&room_id, content, event_type.as_ref())
.await
.map_err(into_err)?,
)
.map_err(into_err)
}
/// Decrypt an event from a room timeline.
///
/// # Arguments
///
/// * `event`, the event that should be decrypted.
/// * `room_id`, the ID of the room where the event was sent to.
#[napi(strict)]
pub async fn decrypt_room_event(
&self,
event: String,
room_id: &identifiers::RoomId,
) -> napi::Result<responses::DecryptedRoomEvent> {
let event = Raw::from_json(RawValue::from_string(event).map_err(into_err)?);
let room_id = room_id.inner.clone();
let room_event = self.inner.decrypt_room_event(&event, &room_id).await.map_err(into_err)?;
Ok(room_event.into())
}
/// Get the status of the private cross signing keys.
///
/// This can be used to check which private cross signing keys we
/// have stored locally.
#[napi]
pub async fn cross_signing_status(&self) -> olm::CrossSigningStatus {
self.inner.cross_signing_status().await.into()
}
/// Sign the given message using our device key and if available
/// cross-signing master key.
#[napi(strict)]
pub async fn sign(&self, message: String) -> types::Signatures {
self.inner.sign(message.as_str()).await.into()
}
/// Shut down the `OlmMachine`.
///
/// The `OlmMachine` cannot be used after this method has been called,
/// otherwise it will panic.
///
/// All associated resources will be closed too, like the crypto storage
/// connections.
///
/// # Safety
///
/// The caller is responsible to **not** use any objects that came from this
/// `OlmMachine` after this `close` method has been called.
#[napi(strict)]
pub fn close(&mut self) {
self.inner = OlmMachineInner::Closed;
}
}
@@ -1,40 +0,0 @@
//! Olm types.
use napi_derive::*;
/// Struct representing the state of our private cross signing keys,
/// it shows which private cross signing keys we have locally stored.
#[napi]
#[derive(Debug)]
pub struct CrossSigningStatus {
inner: matrix_sdk_crypto::olm::CrossSigningStatus,
}
impl From<matrix_sdk_crypto::olm::CrossSigningStatus> for CrossSigningStatus {
fn from(inner: matrix_sdk_crypto::olm::CrossSigningStatus) -> Self {
Self { inner }
}
}
#[napi]
impl CrossSigningStatus {
/// Do we have the master key.
#[napi(getter)]
pub fn has_master(&self) -> bool {
self.inner.has_master
}
/// Do we have the self signing key, this one is necessary to sign
/// our own devices.
#[napi(getter)]
pub fn has_self_signing(&self) -> bool {
self.inner.has_self_signing
}
/// Do we have the user signing key, this one is necessary to sign
/// other users.
#[napi(getter)]
pub fn has_user_signing(&self) -> bool {
self.inner.has_user_signing
}
}
@@ -1,401 +0,0 @@
//! Types to handle requests.
use std::time::Duration;
use matrix_sdk_crypto::requests::{
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
};
use napi::bindgen_prelude::{Either7, FromNapiValue, ToNapiValue};
use napi_derive::*;
use ruma::{
api::client::keys::{
claim_keys::v3::Request as RumaKeysClaimRequest,
upload_keys::v3::Request as RumaKeysUploadRequest,
upload_signatures::v3::Request as RumaSignatureUploadRequest,
},
events::EventContent,
};
use crate::into_err;
/// Data for a request to the `/keys/upload` API endpoint
/// ([specification]).
///
/// Publishes end-to-end encryption keys for the device.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysupload
#[napi]
pub struct KeysUploadRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded string containing the rest of the payload: `device_keys`,
/// `one_time_keys`, `fallback_keys`.
///
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysUploadRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysUpload
}
}
/// Data for a request to the `/keys/query` API endpoint
/// ([specification]).
///
/// Returns the current devices and identity keys for the given users.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysquery
#[napi]
pub struct KeysQueryRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"timeout": …, "one_time_keys": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysQueryRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysQuery
}
}
/// Data for a request to the `/keys/claim` API endpoint
/// ([specification]).
///
/// Claims one-time keys that can be used to establish 1-to-1 E2EE
/// sessions.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keysclaim
#[napi]
pub struct KeysClaimRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded object of form:
///
/// ```json
/// {"event_type": …, "txn_id": …, "messages": …}
/// ```
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysClaimRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysClaim
}
}
/// Data for a request to the `/sendToDevice` API endpoint
/// ([specification]).
///
/// Send an event to a single device or to a group of devices.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3sendtodeviceeventtypetxnid
#[napi]
pub struct ToDeviceRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A string representing the type of event being sent to each devices.
#[napi(readonly)]
pub event_type: String,
/// A string representing a request identifier unique to the access token
/// used to send the request.
#[napi(readonly)]
pub txn_id: String,
/// A JSON-encoded string containing the rest of the payload: `messages`.
///
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
#[napi]
impl ToDeviceRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::ToDevice
}
}
/// Data for a request to the `/keys/signatures/upload` API endpoint
/// ([specification]).
///
/// Publishes cross-signing signatures for the user.
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#post_matrixclientv3keyssignaturesupload
#[napi]
pub struct SignatureUploadRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded string containing the rest of the payload: `signed_keys`.
///
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
#[napi]
impl SignatureUploadRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::SignatureUpload
}
}
/// A customized owned request type for sending out room messages
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3roomsroomidsendeventtypetxnid
#[napi]
pub struct RoomMessageRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A string representing the room to send the event to.
#[napi(readonly)]
pub room_id: String,
/// A string representing the transaction ID for this event.
///
/// Clients should generate an ID unique across requests with the same
/// access token; it will be used by the server to ensure idempotency of
/// requests.
#[napi(readonly)]
pub txn_id: String,
/// A string representing the type of event to be sent.
#[napi(readonly)]
pub event_type: String,
/// A JSON-encoded string containing the message's content.
#[napi(readonly, js_name = "body")]
pub content: String,
}
#[napi]
impl RoomMessageRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::RoomMessage
}
}
/// A request that will back up a batch of room keys to the server
/// ([specification]).
///
/// [specification]: https://spec.matrix.org/unstable/client-server-api/#put_matrixclientv3room_keyskeys
#[napi]
pub struct KeysBackupRequest {
/// The request ID.
#[napi(readonly)]
pub id: String,
/// A JSON-encoded string containing the rest of the payload: `rooms`.
///
/// It represents the body of the HTTP request.
#[napi(readonly)]
pub body: String,
}
#[napi]
impl KeysBackupRequest {
/// Get its request type.
#[napi(getter, js_name = "type")]
pub fn request_type(&self) -> RequestType {
RequestType::KeysBackup
}
}
macro_rules! request {
(
$destination_request:ident from $source_request:ident
$( extracts $( $field_name:ident : $field_type:tt ),+ $(,)? )?
$( $( and )? groups $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),+ $(,)? )?
) => {
impl TryFrom<(String, &$source_request)> for $destination_request {
type Error = napi::Error;
fn try_from(
(request_id, request): (String, &$source_request),
) -> Result<Self, Self::Error> {
request!(
@__try_from $destination_request from $source_request
(request_id = request_id.into(), request = request)
$( extracts [ $( $field_name : $field_type, )+ ] )?
$( groups [ $( $grouped_field_name $( { $grouped_field_transformation } )? , )+ ] )?
)
}
}
};
(
@__try_from $destination_request:ident from $source_request:ident
(request_id = $request_id:expr, request = $request:expr)
$( extracts [ $( $field_name:ident : $field_type:tt ),* $(,)? ] )?
$( groups [ $( $grouped_field_name:ident $( { $grouped_field_transformation:expr } )? ),* $(,)? ] )?
) => {
{
Ok($destination_request {
id: $request_id,
$(
$(
$field_name: request!(@__field $field_name : $field_type ; request = $request),
)*
)?
$(
body: {
let mut map = serde_json::Map::new();
$(
let field = &$request.$grouped_field_name;
$(
let field = {
let $grouped_field_name = field;
$grouped_field_transformation
};
)?
map.insert(stringify!($grouped_field_name).to_owned(), serde_json::to_value(field).map_err(into_err)?);
)*
let object = serde_json::Value::Object(map);
serde_json::to_string(&object).map_err(into_err)?.into()
}
)?
})
}
};
( @__field $field_name:ident : $field_type:ident ; request = $request:expr ) => {
request!(@__field_type as $field_type ; request = $request, field_name = $field_name)
};
( @__field_type as string ; request = $request:expr, field_name = $field_name:ident ) => {
$request.$field_name.to_string().into()
};
( @__field_type as json ; request = $request:expr, field_name = $field_name:ident ) => {
serde_json::to_string(&$request.$field_name).map_err(into_err)?.into()
};
( @__field_type as event_type ; request = $request:expr, field_name = $field_name:ident ) => {
$request.content.event_type().to_string().into()
};
}
request!(KeysUploadRequest from RumaKeysUploadRequest groups device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from RumaKeysQueryRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, device_keys, token);
request!(KeysClaimRequest from RumaKeysClaimRequest groups timeout { timeout.as_ref().map(Duration::as_millis).map(u64::try_from).transpose().map_err(into_err)? }, one_time_keys);
request!(ToDeviceRequest from RumaToDeviceRequest extracts event_type: string, txn_id: string and groups messages);
request!(SignatureUploadRequest from RumaSignatureUploadRequest groups signed_keys);
request!(RoomMessageRequest from RumaRoomMessageRequest extracts room_id: string, txn_id: string, event_type: event_type, content: json);
request!(KeysBackupRequest from RumaKeysBackupRequest groups rooms);
pub type OutgoingRequests = Either7<
KeysUploadRequest,
KeysQueryRequest,
KeysClaimRequest,
ToDeviceRequest,
SignatureUploadRequest,
RoomMessageRequest,
KeysBackupRequest,
>;
pub(crate) struct OutgoingRequest(pub(crate) matrix_sdk_crypto::OutgoingRequest);
impl TryFrom<OutgoingRequest> for OutgoingRequests {
type Error = napi::Error;
fn try_from(outgoing_request: OutgoingRequest) -> Result<Self, Self::Error> {
let request_id = outgoing_request.0.request_id().to_string();
Ok(match outgoing_request.0.request() {
matrix_sdk_crypto::OutgoingRequests::KeysUpload(request) => {
Either7::A(KeysUploadRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysQuery(request) => {
Either7::B(KeysQueryRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysClaim(request) => {
Either7::C(KeysClaimRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::ToDeviceRequest(request) => {
Either7::D(ToDeviceRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::SignatureUpload(request) => {
Either7::E(SignatureUploadRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::RoomMessage(request) => {
Either7::F(RoomMessageRequest::try_from((request_id, request))?)
}
matrix_sdk_crypto::OutgoingRequests::KeysBackup(request) => {
Either7::G(KeysBackupRequest::try_from((request_id, request))?)
}
})
}
}
/// Represent the type of a request.
#[napi]
pub enum RequestType {
/// Represents a `KeysUploadRequest`.
KeysUpload,
/// Represents a `KeysQueryRequest`.
KeysQuery,
/// Represents a `KeysClaimRequest`.
KeysClaim,
/// Represents a `ToDeviceRequest`.
ToDevice,
/// Represents a `SignatureUploadRequest`.
SignatureUpload,
/// Represents a `RoomMessageRequest`.
RoomMessage,
/// Represents a `KeysBackupRequest`.
KeysBackup,
}
@@ -1,202 +0,0 @@
use matrix_sdk_common::deserialized_responses::{AlgorithmInfo, EncryptionInfo};
use matrix_sdk_crypto::IncomingResponse;
use napi_derive::*;
pub(crate) use ruma::api::client::{
backup::add_backup_keys::v3::Response as KeysBackupResponse,
keys::{
claim_keys::v3::Response as KeysClaimResponse, get_keys::v3::Response as KeysQueryResponse,
upload_keys::v3::Response as KeysUploadResponse,
upload_signatures::v3::Response as SignatureUploadResponse,
},
message::send_message_event::v3::Response as RoomMessageResponse,
to_device::send_event_to_device::v3::Response as ToDeviceResponse,
};
use ruma::api::IncomingResponse as RumaIncomingResponse;
use crate::{encryption, identifiers, into_err, requests::RequestType};
pub(crate) fn response_from_string(body: &str) -> http::Result<http::Response<Vec<u8>>> {
http::Response::builder().status(200).body(body.as_bytes().to_vec())
}
/// Intermediate private type to store an incoming owned response,
/// without the need to manage lifetime.
pub(crate) enum OwnedResponse {
KeysUpload(KeysUploadResponse),
KeysQuery(KeysQueryResponse),
KeysClaim(KeysClaimResponse),
ToDevice(ToDeviceResponse),
SignatureUpload(SignatureUploadResponse),
RoomMessage(RoomMessageResponse),
KeysBackup(KeysBackupResponse),
}
impl From<KeysUploadResponse> for OwnedResponse {
fn from(response: KeysUploadResponse) -> Self {
OwnedResponse::KeysUpload(response)
}
}
impl From<KeysQueryResponse> for OwnedResponse {
fn from(response: KeysQueryResponse) -> Self {
OwnedResponse::KeysQuery(response)
}
}
impl From<KeysClaimResponse> for OwnedResponse {
fn from(response: KeysClaimResponse) -> Self {
OwnedResponse::KeysClaim(response)
}
}
impl From<ToDeviceResponse> for OwnedResponse {
fn from(response: ToDeviceResponse) -> Self {
OwnedResponse::ToDevice(response)
}
}
impl From<SignatureUploadResponse> for OwnedResponse {
fn from(response: SignatureUploadResponse) -> Self {
Self::SignatureUpload(response)
}
}
impl From<RoomMessageResponse> for OwnedResponse {
fn from(response: RoomMessageResponse) -> Self {
OwnedResponse::RoomMessage(response)
}
}
impl From<KeysBackupResponse> for OwnedResponse {
fn from(r: KeysBackupResponse) -> Self {
Self::KeysBackup(r)
}
}
impl TryFrom<(RequestType, http::Response<Vec<u8>>)> for OwnedResponse {
type Error = napi::Error;
fn try_from(
(request_type, response): (RequestType, http::Response<Vec<u8>>),
) -> Result<Self, Self::Error> {
match request_type {
RequestType::KeysUpload => {
KeysUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysQuery => {
KeysQueryResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysClaim => {
KeysClaimResponse::try_from_http_response(response).map(Into::into)
}
RequestType::ToDevice => {
ToDeviceResponse::try_from_http_response(response).map(Into::into)
}
RequestType::SignatureUpload => {
SignatureUploadResponse::try_from_http_response(response).map(Into::into)
}
RequestType::RoomMessage => {
RoomMessageResponse::try_from_http_response(response).map(Into::into)
}
RequestType::KeysBackup => {
KeysBackupResponse::try_from_http_response(response).map(Into::into)
}
}
.map_err(into_err)
}
}
impl<'a> From<&'a OwnedResponse> for IncomingResponse<'a> {
fn from(response: &'a OwnedResponse) -> Self {
match response {
OwnedResponse::KeysUpload(response) => IncomingResponse::KeysUpload(response),
OwnedResponse::KeysQuery(response) => IncomingResponse::KeysQuery(response),
OwnedResponse::KeysClaim(response) => IncomingResponse::KeysClaim(response),
OwnedResponse::ToDevice(response) => IncomingResponse::ToDevice(response),
OwnedResponse::SignatureUpload(response) => IncomingResponse::SignatureUpload(response),
OwnedResponse::RoomMessage(response) => IncomingResponse::RoomMessage(response),
OwnedResponse::KeysBackup(response) => IncomingResponse::KeysBackup(response),
}
}
}
/// A decrypted room event.
#[napi]
pub struct DecryptedRoomEvent {
/// The JSON-encoded decrypted event.
#[napi(readonly)]
pub event: String,
encryption_info: Option<EncryptionInfo>,
}
#[napi]
impl DecryptedRoomEvent {
/// The user ID of the event sender, note this is untrusted data
/// unless the `verification_state` is as well trusted.
#[napi(getter)]
pub fn sender(&self) -> Option<identifiers::UserId> {
Some(identifiers::UserId::from(self.encryption_info.as_ref()?.sender.clone()))
}
/// The device ID of the device that sent us the event, note this
/// is untrusted data unless `verification_state` is as well
/// trusted.
#[napi(getter)]
pub fn sender_device(&self) -> Option<identifiers::DeviceId> {
Some(self.encryption_info.as_ref()?.sender_device.as_ref()?.clone().into())
}
/// The Curve25519 key of the device that created the megolm
/// decryption key originally.
#[napi(getter)]
pub fn sender_curve25519_key(&self) -> Option<String> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, .. } => curve25519_key.clone(),
})
}
/// The signing Ed25519 key that have created the megolm key that
/// was used to decrypt this session.
#[napi(getter)]
pub fn sender_claimed_ed25519_key(&self) -> Option<String> {
match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { sender_claimed_keys, .. } => {
sender_claimed_keys.get(&ruma::DeviceKeyAlgorithm::Ed25519).cloned()
}
}
}
/// Chain of Curve25519 keys through which this session was
/// forwarded, via `m.forwarded_room_key` events.
#[napi(getter)]
pub fn forwarding_curve25519_key_chain(&self) -> Vec<String> {
vec![]
}
/// The verification state of the device that sent us the event,
/// note this is the state of the device at the time of
/// decryption. It may change in the future if a device gets
/// verified or deleted.
#[napi]
pub fn shield_state(&self, strict: bool) -> Option<encryption::ShieldState> {
let state = &self.encryption_info.as_ref()?.verification_state;
if strict {
Some(state.to_shield_state_strict().into())
} else {
Some(state.to_shield_state_lax().into())
}
}
}
impl From<matrix_sdk_common::deserialized_responses::TimelineEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::TimelineEvent) -> Self {
Self { event: value.event.json().get().to_owned(), encryption_info: value.encryption_info }
}
}
@@ -1,49 +0,0 @@
//! `GET /_matrix/client/*/sync`.
use napi_derive::*;
use crate::identifiers;
/// Information on E2E device updates.
#[napi]
pub struct DeviceLists {
pub(crate) inner: ruma::api::client::sync::sync_events::DeviceLists,
}
#[napi]
impl DeviceLists {
/// Create an empty `DeviceLists`.
#[napi(constructor, strict)]
pub fn new(
changed: Option<Vec<&identifiers::UserId>>,
left: Option<Vec<&identifiers::UserId>>,
) -> Self {
let mut inner = ruma::api::client::sync::sync_events::DeviceLists::default();
inner.changed = changed.into_iter().flatten().map(|user| user.inner.clone()).collect();
inner.left = left.into_iter().flatten().map(|user| user.inner.clone()).collect();
Self { inner }
}
/// Returns true if there are no device list updates.
#[napi]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// List of users who have updated their device identity keys or
/// who now share an encrypted room with the client since the
/// previous sync.
#[napi(getter)]
pub fn changed(&self) -> Vec<identifiers::UserId> {
self.inner.changed.iter().map(|user| identifiers::UserId::from(user.to_owned())).collect()
}
/// List of users who no longer share encrypted rooms since the
/// previous sync response.
#[napi(getter)]
pub fn left(&self) -> Vec<identifiers::UserId> {
self.inner.left.iter().map(|user| identifiers::UserId::from(user.to_owned())).collect()
}
}
@@ -1,11 +0,0 @@
use napi_derive::*;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
/// Subscribe to tracing events, i.e. turn on logs.
#[napi]
pub fn init_tracing() {
tracing_subscriber::registry()
.with(fmt::layer())
.with(EnvFilter::from_env("MATRIX_LOG"))
.init();
}
@@ -1,156 +0,0 @@
use std::collections::HashMap;
use napi_derive::*;
use crate::{
identifiers::{DeviceKeyId, UserId},
vodozemac::Ed25519Signature,
};
#[napi]
#[derive(Default)]
pub struct Signatures {
inner: matrix_sdk_crypto::types::Signatures,
}
impl From<matrix_sdk_crypto::types::Signatures> for Signatures {
fn from(inner: matrix_sdk_crypto::types::Signatures) -> Self {
Self { inner }
}
}
#[napi]
impl Signatures {
/// Creates a new, empty, signatures collection.
#[napi(constructor)]
pub fn new() -> Self {
matrix_sdk_crypto::types::Signatures::new().into()
}
/// Add the given signature from the given signer and the given key ID to
/// the collection.
#[napi(strict)]
pub fn add_signature(
&mut self,
signer: &UserId,
key_id: &DeviceKeyId,
signature: &Ed25519Signature,
) -> Option<MaybeSignature> {
self.inner
.add_signature(signer.inner.clone(), key_id.inner.clone(), signature.inner)
.map(Into::into)
}
/// Try to find an Ed25519 signature from the given signer with
/// the given key ID.
#[napi(strict)]
pub fn get_signature(&self, signer: &UserId, key_id: &DeviceKeyId) -> Option<Ed25519Signature> {
self.inner.get_signature(signer.inner.as_ref(), key_id.inner.as_ref()).map(Into::into)
}
/// Get the map of signatures that belong to the given user.
#[napi(strict)]
pub fn get(&self, signer: &UserId) -> Option<HashMap<String, MaybeSignature>> {
self.inner.get(signer.inner.as_ref()).map(|map| {
map.iter()
.map(|(device_key_id, maybe_signature)| {
(device_key_id.as_str().to_owned(), maybe_signature.clone().into())
})
.collect()
})
}
/// Remove all the signatures we currently hold.
#[napi]
pub fn clear(&mut self) {
self.inner.clear();
}
/// Do we hold any signatures or is our collection completely
/// empty.
#[napi(getter)]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
/// How many signatures do we currently hold.
#[napi(getter)]
pub fn count(&self) -> usize {
self.inner.signature_count()
}
}
/// Represents a potentially decoded signature (but not a validated
/// one).
#[napi]
pub struct Signature {
inner: matrix_sdk_crypto::types::Signature,
}
impl From<matrix_sdk_crypto::types::Signature> for Signature {
fn from(inner: matrix_sdk_crypto::types::Signature) -> Self {
Self { inner }
}
}
#[napi]
impl Signature {
/// Get the Ed25519 signature, if this is one.
#[napi(getter)]
pub fn ed25519(&self) -> Option<Ed25519Signature> {
self.inner.ed25519().map(Into::into)
}
/// Convert the signature to a base64 encoded string.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
type MaybeSignatureInner =
Result<matrix_sdk_crypto::types::Signature, matrix_sdk_crypto::types::InvalidSignature>;
/// Represents a signature that is either valid _or_ that could not be
/// decoded.
#[napi]
pub struct MaybeSignature {
inner: MaybeSignatureInner,
}
impl From<MaybeSignatureInner> for MaybeSignature {
fn from(inner: MaybeSignatureInner) -> Self {
Self { inner }
}
}
#[napi]
impl MaybeSignature {
/// Check whether the signature has been successfully decoded.
#[napi(getter)]
pub fn is_valid(&self) -> bool {
self.inner.is_ok()
}
/// Check whether the signature could not be successfully decoded.
#[napi(getter)]
pub fn is_invalid(&self) -> bool {
self.inner.is_err()
}
/// The signature, if successfully decoded.
#[napi(getter)]
pub fn signature(&self) -> Option<Signature> {
self.inner.as_ref().cloned().map(Into::into).ok()
}
/// The base64 encoded string that is claimed to contain a
/// signature but could not be decoded, if any.
#[napi(getter)]
pub fn invalid_signature_source(&self) -> Option<String> {
match &self.inner {
Ok(_) => None,
Err(signature) => Some(signature.source.clone()),
}
}
}
@@ -1,113 +0,0 @@
use napi_derive::*;
use crate::into_err;
/// An Ed25519 public key, used to verify digital signatures.
#[napi]
#[derive(Clone)]
pub struct Ed25519PublicKey {
inner: vodozemac::Ed25519PublicKey,
}
#[napi]
impl Ed25519PublicKey {
/// The number of bytes an Ed25519 public key has.
#[napi(getter)]
pub fn length(&self) -> u32 {
vodozemac::Ed25519PublicKey::LENGTH as u32
}
/// Serialize an Ed25519 public key to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// An Ed25519 digital signature, can be used to verify the
/// authenticity of a message.
#[napi]
pub struct Ed25519Signature {
pub(crate) inner: vodozemac::Ed25519Signature,
}
impl From<vodozemac::Ed25519Signature> for Ed25519Signature {
fn from(inner: vodozemac::Ed25519Signature) -> Self {
Self { inner }
}
}
#[napi]
impl Ed25519Signature {
/// Try to create an Ed25519 signature from an unpadded base64
/// representation.
#[napi(constructor, strict)]
pub fn new(signature: String) -> napi::Result<Self> {
Ok(Self {
inner: vodozemac::Ed25519Signature::from_base64(signature.as_str())
.map_err(into_err)?,
})
}
/// Serialize a Ed25519 signature to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// A Curve25519 public key.
#[napi]
#[derive(Clone)]
pub struct Curve25519PublicKey {
inner: vodozemac::Curve25519PublicKey,
}
#[napi]
impl Curve25519PublicKey {
/// The number of bytes a Curve25519 public key has.
#[napi(getter)]
pub fn length(&self) -> u32 {
vodozemac::Curve25519PublicKey::LENGTH as u32
}
/// Serialize an Curve25519 public key to an unpadded base64
/// representation.
#[napi]
pub fn to_base64(&self) -> String {
self.inner.to_base64()
}
}
/// Struct holding the two public identity keys of an account.
#[napi]
pub struct IdentityKeys {
ed25519: Ed25519PublicKey,
curve25519: Curve25519PublicKey,
}
#[napi]
impl IdentityKeys {
/// The Ed25519 public key, used for signing.
#[napi(getter)]
pub fn ed25519(&self) -> Ed25519PublicKey {
self.ed25519.clone()
}
/// The Curve25519 public key, used for establish shared secrets.
#[napi(getter)]
pub fn curve25519(&self) -> Curve25519PublicKey {
self.curve25519.clone()
}
}
impl From<matrix_sdk_crypto::olm::IdentityKeys> for IdentityKeys {
fn from(value: matrix_sdk_crypto::olm::IdentityKeys) -> Self {
Self {
ed25519: Ed25519PublicKey { inner: value.ed25519 },
curve25519: Curve25519PublicKey { inner: value.curve25519 },
}
}
}

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