Compare commits

...

624 Commits

Author SHA1 Message Date
Benjamin Kampmann fefd2a6b68 chore: crates.io only accepts versioned git deps 2022-09-28 17:15:09 +02:00
Benjamin Kampmann 269bf3a706 chore: remove old release notes 2022-09-28 17:07:37 +02:00
Benjamin Kampmann 91d1ee9f09 chore: fix typos in Upgrade guide 2022-09-28 17:07:37 +02:00
Benjamin Kampmann b22fe63476 chore: More minor changes of note 2022-09-28 17:07:37 +02:00
Benjamin Kampmann 92467af4a0 docs: Upgrading typo fixes 2022-09-28 17:07:37 +02:00
Benjamin Kampmann f1f1c1bba6 chore: Version bump 2022-09-28 17:07:37 +02:00
Benjamin Kampmann ff82d6420d chore: specify which crates to release and which not 2022-09-28 17:07:37 +02:00
Benjamin Kampmann b9409b7c0f docs: more migrations guide 2022-09-28 17:07:37 +02:00
Benjamin Kampmann ea51935601 docs: Clarifications in the Upgrade guide 2022-09-28 17:07:37 +02:00
Benjamin Kampmann 2fff5d916b docs: More Upgrade guidance 2022-09-28 17:07:37 +02:00
Benjamin Kampmann d16faee68b docs: First outline of upgrades guide 2022-09-28 17:07:37 +02:00
Benjamin Kampmann df9b60a5bc chore: Ignore flaky redaction tests 2022-09-28 17:07:10 +02:00
Damir Jelić 1a57170970 test(crypto): Make sure we don't accept megolm encrypted events over to-device 2022-09-28 16:03:17 +02:00
Damir Jelić 41449d2cc3 test(crypto): Test that we reject forwarded room keys from other users 2022-09-28 16:03:17 +02:00
Damir Jelić 093fb5d0aa fix(crypto): Only accept forwarded room keys from our own trusted devices 2022-09-28 16:03:17 +02:00
Ivan Enderlin 513afa57ef feat(indexeddb): Introduce the experimental-nodejs feature
feat(indexeddb): Introduce the `experimental-nodejs` feature
2022-09-28 14:57:55 +02:00
Ivan Enderlin 25632f04d4 feat(crypto-js): Update to napi-rs 2.9.1
feat(crypto-js): Update to `napi-rs` 2.9.1
2022-09-28 14:52:03 +02:00
Ivan Enderlin 7922786e0c chore: Fix formatting. 2022-09-28 14:34:16 +02:00
Ivan Enderlin 9a006c58f5 feat(crypto-js): Update to napi-rs 2.9.1 2022-09-28 14:30:40 +02:00
Ivan Enderlin 9f4385e639 feat(indexeddb): Introduce the experimental-nodejs feature.
By default, the new `experimental-nodejs` feature is
disabled. However, when enabled, it uses our own fork of
`indexed_db_futures` that make it work on Node.js, and so this entire
`matrix-sdk-indexebdb` crate.
2022-09-28 14:27:37 +02:00
Damir Jelić e79b683560 fix(crypto): Drop the redundant event_type field from ToDeviceEvent<C> 2022-09-28 11:27:44 +02:00
Damir Jelić 27e4675df0 fix(crypto) Fix the zeroizing serialization of to-device events
The crypto crate consumes to-device events, if needed decrypts them, and
in the end reserializes while zeroizing secrets.

It needs to do this reserialization cycle as loslessly as possible, this
is why we have a bunch of BTreeMaps lying in events and contents that
capture unknown fields.

The Deserialize implementation of ToDeviceEvent<C> put the event type
into this BTreeMap but the Serialize implementation figures it out from
the EventType trait of the content.

This patch simplifies things by putting the event type into
ToDeviceEvent<C>, this also means that we can just derive the Serialize
implementation for ToDeviceEvent<C>.
2022-09-27 21:33:06 +02:00
Benjamin Kampmann b5bd6dfee9 fix: Apply redactions to room state events in database, too (#917)
fixes #890
2022-09-27 09:24:53 +02:00
Ivan Enderlin f8b02d1a11 feat(crypto-js): Implement OlmMachine.export_room_keys and .import_room_keys
feat(crypto-js): Implement `OlmMachine.export_room_keys` and `.import_room_keys`
2022-09-27 07:26:51 +02:00
Jonas Platte 0a5ebe4dce feat(sdk): Make EventHandlerDropGuard public (#1055) 2022-09-26 18:18:12 +02:00
Benjamin Kampmann e8278b5d68 feat: add Result return value to sync_* (#1037)
Inspired by the changes in #1013 I was thinking about the use case for `sync_*` and how we handle error cases. Most notably while we give the callback the option to stop the loop, we don't really give an indication to the outside, how to interpret that cancellation: was there a failure? should we restart?

Take e.g. a connectivity issue on the wire, we'd constantly loop and just `warn`, what you might or might not see. Even if you handle that in the `sync_with_result_callback` and thus break the loop, the outer caller now still doesn't know whether everything is honky dory or whether they should restart. 

This Changes reworks that area by having all the `sync` return `Result<(), Error>`, where `()` means it was ended by the inner callback (which in `sync()` never occurs) or `Error` is the error either the inner `result_callback` found or the that was coming from the `send` in the first place. Thus allowing us to e.g. back down to sync as it was a dead wire or restart it if there was only a temporary problem. Making all that a just a bit more "rust-y".
2022-09-26 17:14:15 +02:00
Jonas Platte f054141e66 chore: Revert "feat(sdk): Make room actions return typed room objects" 2022-09-26 14:13:25 +02:00
Jonas Platte 1a6d5e7b26 Revert "feat(sdk): Make room actions return typed room objects"
This reverts commit febfcf9796.
2022-09-26 14:01:52 +02:00
Jonas Platte d3e5a3b7db Revert "test: Add test for repeated joining and leaving"
This partially reverts commit df4ee7db4c.
The changes to logging and ci-start.sh are kept.
2022-09-26 12:31:12 +02:00
Jonas Platte 92809590b1 Revert "feat(examples): Add an example that lets you create rooms"
This reverts commit 01f8ed10aa.
2022-09-26 12:23:08 +02:00
Jonas Platte 00d388f3d3 Revert "feat(sdk): Make room actions return typed room objects"
This reverts commit febfcf9796.
2022-09-26 12:19:36 +02:00
Jonas Platte 59901b6349 Update Cargo.lock 2022-09-26 12:19:36 +02:00
Johannes Becker 914ac5279e chore: Make clippy happy 2022-09-23 17:01:01 +02:00
Johannes Becker c23976f975 refactor(appservice): Move example to top-level 2022-09-23 17:01:01 +02:00
Emelie Graven cb94d60e0e feat(appservice): Add default request config
This adds a default RequestConfig to use when creating new virtual
clients.
2022-09-22 14:25:00 +02:00
Ivan Enderlin df50a5446f test(crypto-js): Test InboundGrounSession.sessionId and .hasBeenImported. 2022-09-22 11:31:28 +02:00
Ivan Enderlin 62ab263d0e test(crypto-js): Test OlmMachine.(en|de)crypt_exported_room_keys. 2022-09-22 11:27:12 +02:00
Emelie Graven 2818551d62 refactor(appservice)!: Use builder pattern 2022-09-22 10:38:17 +02:00
Emelie Graven c29b0f154a chore: Add rust.vim edition workaround 2022-09-22 10:38:17 +02:00
Ivan Enderlin 6fa04a0ba1 feat(crypto-js): Implement OlmMachine.(en|de)crypt_exported_room_keys. 2022-09-21 18:00:15 +02:00
Ivan Enderlin dee14c4ee4 doc(crypto-js): Improve documentation of OlmMachine.import_room_keys. 2022-09-21 17:50:28 +02:00
Ivan Enderlin 5b919dd840 test(crypto-js): Test OlmMachine.import_room_keys. 2022-09-21 17:46:23 +02:00
Ivan Enderlin 406fc58720 feat(crypto-js): Implement OlmMachine.import_room_keys. 2022-09-21 17:37:23 +02:00
Ivan Enderlin b4cec6e1bb feat(crypto-js): Implement OlmMachine.export_room_keys. 2022-09-21 16:03:51 +02:00
Ivan Enderlin 6be0fc1fae feat(sdk): Rename (export|import)_keys to (export|import)_room_keys
feat(sdk): Rename `(export|import)_keys` to `(export|import)_room_keys`
2022-09-21 16:03:29 +02:00
Ivan Enderlin 782b256ea9 test(crypto): Rename OlmMachine.export_keys to OlmMachine.export_room_keys. 2022-09-21 13:43:25 +02:00
Ivan Enderlin 82670022f9 feat(crypto-ffi): Update olm.udl. 2022-09-21 13:24:25 +02:00
Ivan Enderlin b8c41e805b feat(sdk): Rename decrypt_key_export to decrypt_room_key_export. 2022-09-21 13:18:24 +02:00
Ivan Enderlin e3d63c5593 feat(sdk): Rename encrypt_key_export to encrypt_room_key_export. 2022-09-21 13:16:08 +02:00
Ivan Enderlin 0222a8fec2 feat(sdk): Rename OlmMachine.import_keys to .import_room_keys.
Because that's what it does :-).
2022-09-21 13:12:07 +02:00
Ivan Enderlin b442247946 feat(sdk): Rename OlmMachine.export_keys to .export_room_keys.
Because that's what it does :-).
2022-09-21 13:03:42 +02:00
Ivan Enderlin c0ebeee730 feat(crypto-js): Implement OlmMachine.get_identity
feat(crypto-js): Implement `OlmMachine.get_identity`
2022-09-21 10:44:18 +02:00
Stefan Ceriu a46b8f392a Switch ffi layer logs output to stderr instead of stdout 2022-09-21 08:24:09 +02:00
Jonas Platte b12da9d4db refactor!: Move JS-specific functionality behind a Cargo feature
… for matrix-sdk, matrix-sdk-base, matrix-sdk-common and matrix-sdk-crypto.
matrix-sdk-indexeddb as well as the JS bindings and wasm_command_bot are
left as-is because they will likely always require JS.
2022-09-20 14:08:21 +02:00
Stefan Ceriu 6f5681f7c2 feat(bindings): Expose method for fetching media thumbnails 2022-09-19 10:56:22 +00:00
Jonas Platte fee0db03c8 ci: Don't collect coverage for labs 2022-09-19 12:15:13 +02:00
Jonas Platte b2ce906bce ci: Don't report coverage inside test code 2022-09-19 12:15:13 +02:00
Jonas Platte e45d6f45fd refactor: Replace str::to_string with to_owned 2022-09-16 17:45:43 +02:00
Ivan Enderlin 84aa85958c test(crypto-js): Test OlmMachine.getIdentity. 2022-09-16 11:23:32 +02:00
Jonas Platte b941c16b7d chore(sdk): Upgrade derive_builder 2022-09-16 11:17:24 +02:00
Ivan Enderlin 1a5379881e test(crypto-js): Test EventId. 2022-09-16 11:11:06 +02:00
Ivan Enderlin 0c4b85e1f9 feat(crypto-js): Implement OwnUserIdentity and UserIdentity. 2022-09-16 11:02:34 +02:00
Ivan Enderlin 602dbe42f2 doc(crypto): Fix a typo. 2022-09-16 10:57:02 +02:00
Ivan Enderlin d3d316ee6c chore(crypto-js): Add helper to cast from JS Array to Vec<T>. 2022-09-16 10:46:28 +02:00
Jonas Platte 494969ed49 chore: Fix clippy lints 2022-09-15 20:48:22 +02:00
Kévin Commaille 34ed04958f fix(sdk): Export RoomKeyImportError 2022-09-15 18:35:59 +02:00
Jonas Platte 8da456055d refactor: Use event handlers in emoji_verification example 2022-09-15 18:33:58 +02:00
Jonas Platte ef462c786a refactor(sdk): Clean up sync_with_callback implementation 2022-09-15 17:53:32 +02:00
Benjamin Kampmann 1a50f42402 fix(jack-in): limit log4rs features to minimum 2022-09-15 17:08:09 +02:00
Benjamin Kampmann 020a75d55c feat: implement " MSC 3575: sliding sync " behind a feature flag (#728)
* starting with jack-in

* starting by flying tui

* connecting to real server, showing info

* add .env to gitignore

* infrastructure for tests

* display loading time for syncv2

* minor design updates

* initial sync

* finalise first edition of sliding sync

* directly link to sliding sync and show rooms list

* nicer UI, toggle logs

* passing through sliding sync homeserver

* separate syncs and disable v2 autostart

* selecting rooms

* nicer view

* configurable batches and more default needed events

* selecting rooms

* calculate and show status info per room

* precalculated room stats

* restructure code to allow for cancellation of streams

* finish up merge updates

* fix calculation error in room list len

* cleaning up system flow

* fixing sync up

* new multi-view API

* move sliding sync in separate module

* fixing format

* adding and clearning views

* expose filters and timeline limits

* renamed

* adding room subscriptions to sliding sync

* update summary

* live fetching and subscriptions in jack-in

* subscribe to selected room

* starting to switch to tuireal - using example

* status bar and first linkup

* re-adding rooms

* implementing port for customised update event

* showing details and timeline

* fix formatting

* cleaner UI, updating details quickly

* make it green

* implement other Ops

* proper handling of invalidation

* saving sliding sync results to db, too

* saving new prev_batch field if given

* split events and timeline

* cleaning up

* live updates

* upgrading to latest ruma and matrix-sdk

* Update tui-logger to fix the broken build

* fixing latest ruma sliding-sync-branch updates

* feat: first set of ffi sliding sync

* expose sliding sync via FFI

* implement un-/subscribe

* implement view state updates

* updating to latest JSON format and ruma update

* implementing room selecting for new data model

* fixing room selection

* fixing feature flag requirements for sliding-sync

* fixing style, clippy and docs

* style(sliding-sync): fixing rust format styles

* fix(ffi): fixing sliding sync merge failure

* fix(jack-in): update jack-in to latest ruma

* fix(sliding-sync): need to have a version set before polling proxy

* expose sliding sync builder over ffi

* add SlidingSyncViewBuilder

* add forgotten changes on sdk itself

* new file logger feature on jack for deeper logging

* fix(http-client): log the raw request we are sending out

* feat(sliding-sync): better logging

* fix(sliding-sync): switch to full-listen after reaching live state in full-sync and make sure we replace the correct entries

* feat(ffi): expose sliding sync view ranges

* fix(ffi): fixing sliding sync start_sync loop to actually loop

* feat(sliding-sync): allow lookup of room data

* feat(sliding-sync-ffi): fetching name of room

* feat(ffi): expose unread_notifications of rooms

* feat(ffi): stoppable spawn for sliding sync

* fix(ffi): expose has_unread_notifications on room

* feat(sliding-sync): latest room message

* fix(sliding-sync): update to latest ruma layout

* doc(sliding-sync): adding docs to builder fns

* feat(sliding-sync): extra signal on the view to inform about general updates

* fix(sync-v4-ffi): expose new callbacks via ffi

* fix(sliding-sync): reduce default required states to make things faster

* fix(sliding-sync): fix build params

* feat(jack-in): extended instrumentation

* fix(sliding-sync): unbreak faulty feature-gate

* fix(sliding-sync-ffi): mut where mut is due

* fix(sdk): allow separate homeserver on every request to unbreak using send on client while in sliding sync on a proxy

* fix(jack-in): update to latest dependencies, that work

* feat(ffi): helper to patch sliding sync room into regular room

* style(jack-in): cargo fmt

* fix(sliding-sync): Update to latest ruma changes

* fix(sliding-sync): fix missing FFI updates to latest ruma

* feat(sliding-sync)!: simplify stream cancellation, cancel ffi sync if already existing

* fix: timeline backwards pagination must work without synctoken

* fix(sliding-sync): clarify order of messaes in alive TL; pick correct last item

* fix: update view delegate api for clarity

* style(jack-in): fix cargo warnings

* feat(sliding-sync): update room details

* fix(sliding-sync): only update room info selectively when given

* fix(sliding-sync-ffi): convert and store counts as u32, check against 0 for has notificaitons

* style: cargo fmt, file endings and a few other minor style fixes

* docs(jack-in): improving CLI and Readme

* feat(sliding-sync): allow setting of required event_states on viewbuilder

* style(sliding-sync): docs and minor fixes

* style(sliding-sync): various clippy fixes

* style(jack-in): clippy suggestions applied

* fix(sliding-sync): Delegate becomes observer

* test(sdk): adding test for request config

* docs: Fixing copyright header

* style(ffi): Nicer naming of params for observer

* fix(ffi): sliding sync is not optional for now

* fix(sdk): remove superflusous tracing instrumentation

* fix(sdk): use structured logging

* fix(jack-in): removed unneded log import

* fix(jack-in): use server_name rather than deprecated user_id on ClientBuilder

* style: typo and clippy

* style(sliding-sync): clippy and formatting

* fix(sliding-sync): cleaning up minor syntax issues

* fix: remove unneded feature-definition section

* fix(sliding-sync): minor fixes as per review

* fix(sliding-sync): Make Builders owned

* fix(sliding-sync): more minor style improvements

* fix(sliding-sync): minor style improvements

* fix(sliding-sync): remove homeserver from RequestConfig, use specific internal fn instead

Co-authored-by: Stefan Ceriu <stefanc@matrix.org>
2022-09-15 11:45:29 +00:00
Ivan Enderlin f4e0c6e243 feat(crypto-js): Start implementing OlmMachine.get_identity. 2022-09-15 10:32:57 +02:00
Ivan Enderlin 01f1b9b846 feat(crypto-js): Implement EventId. 2022-09-15 10:32:57 +02:00
Ivan Enderlin abcd287496 chore(crypto-js): Simplify code with a lovely macro. 2022-09-15 10:32:57 +02:00
Ivan Enderlin cf96a3ba2e feat(crypto-js): Implement key verification
feat(crypto-js): Implement key verification
2022-09-15 10:13:51 +02:00
Ivan Enderlin 9d2e0fe8ad doc(crypto-js): Fix typos. 2022-09-15 09:59:27 +02:00
Jonas Platte 83d5e567eb feat(bindings): Update send_reply to accept markdown 2022-09-14 22:10:16 +02:00
Benjamin Kampmann a47d8669cd feat: log out facilities
Merge pull request #1013 from matrix-org/ismail/logout

Add logout facilities to `client` and helpers to ffi for tracking the soft-logout issued by a server. Further more this adapts the `sync_with_callback` by adding a new `sync_with_result_callback` that hands the entire `Result` to the callback.
2022-09-14 15:15:47 +02:00
ismailgulek 0643292d76 Fix formatting 2022-09-14 15:07:05 +03:00
ismailgulek 8a26ba8343 Add a warning on sync error 2022-09-14 14:29:30 +03:00
ismailgulek cc1c6aedcb Revert sync_with_callback api call changes 2022-09-14 14:17:30 +03:00
ismailgulek 55cf573142 Implement the new sync with result callback method 2022-09-14 14:10:01 +03:00
Stefan Ceriu c5006081e6 feat(bindings): Add method for sending plain text replies 2022-09-14 10:21:23 +00:00
Ivan Enderlin 4c4fcf91c1 chore(crypto-js): Inline vodozemac dependency. 2022-09-14 10:01:03 +02:00
Ivan Enderlin 6842fb97fd chore(crypto-js): Make Clippy happy. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 8027a3036c test(crypto-js): Remove dependency to canvas. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 1d9ac6e60c chore(crypto-js): Fix typos. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 2f35b2cfc6 feat(crypto-js): QrCodeScan implements Debug. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 7edd6a148c doc(crypto-js): Fix module documentation. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 3b526ea412 doc(crypto-js): Add missing documentation. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 1c50cee5d7 feat(crypto-js): Implement Sas and Qr cancel* methods. 2022-09-14 09:58:09 +02:00
Ivan Enderlin cb95c59194 test(crypto-js): Test m.key.verification.start and .done for QR code. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 792b4581ab feat(crypto-js): Implement Qr.reciprocate and .confirm_scanning. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 58ea598c68 feat(crypto-js): Implement VerificationRequest.scan_qr_code. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 581c537396 test(crypto-js): Properly test `Qr.toQrCode. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 9834b67bd5 feat(crypto-js): QrCode.renderIntoBuffer returns an Uint8ClampedArray. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 6f89d02599 test(crypto-js): Test QrCode.render_into_buffer. 2022-09-14 09:58:09 +02:00
Ivan Enderlin cbb5080837 test(crypto-js): Properly test Qr.toBytes. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 6a05f834a9 test(crypto-js): Test Qr.toBytes. 2022-09-14 09:58:09 +02:00
Ivan Enderlin c7e0b3ee31 test(crypto-js): Testing key verification workflow until QR code generation. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 0d57983e1b feat(crypto-js): Implement VerificationRequest.generate_qr_code. 2022-09-14 09:58:09 +02:00
Ivan Enderlin bbfc076c7f test(crypto-js): Inject bootstrap cross signing keys when setting up machines. 2022-09-14 09:58:09 +02:00
Ivan Enderlin e367d8574d feat(crypto-js): Implement OlmMachine.bootstrap_cross_signing. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 1be17d354d feat(crypto-js): Implement OlmMachine.(export|import)_cross_signing_keys. 2022-09-14 09:58:09 +02:00
Ivan Enderlin e8331cc40c feat(crypto-js): Enable the qrcode feature by default. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 71a2fac46f test(crypto-js): Reorganize the tests a little bit. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 8948333e1e test(crypto-js): Test until m.key.verification.done \o/. 2022-09-14 09:58:09 +02:00
Ivan Enderlin c471a6fb4d test(crypto-js): Split the Key Verification test case into a test suite. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 6239d31bcf test(crypto-js): Test the Emoji and decimals implementations. 2022-09-14 09:58:09 +02:00
Ivan Enderlin b5a8103023 feat(crypto-js): Implement Sas.accept. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 95709bb4b3 test(crypto-js): Test the Sas implementation. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 14f22979c0 feat(crypto-js): Implement VerificationRequest.start_sas. 2022-09-14 09:58:09 +02:00
Ivan Enderlin e6141d8efc test(crypto-js): Continue to test m.key.verification.request and .ready. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 155b187d45 test(crypto-js): Write first tests for key verification. 2022-09-14 09:58:09 +02:00
Ivan Enderlin e659c724cd chore(crypto-js): Some methods have been renamed. 2022-09-14 09:58:09 +02:00
Ivan Enderlin e00b9221b9 feat(crypto-js): Implement Device.request_verification. 2022-09-14 09:58:09 +02:00
Ivan Enderlin b6f01b3cec feat(crypto-js): Implement the Device and UserDevice API. 2022-09-14 09:58:09 +02:00
Ivan Enderlin 53e21e0c26 feat(crypto-js): Start implementation key verification API. 2022-09-14 09:58:06 +02:00
Ivan Enderlin 9155989060 feat(crypto): Simplify code and add documentation. 2022-09-14 09:56:33 +02:00
Doug bb62437369 feat(bindings): Expose redact method to FFI 2022-09-13 23:06:11 +02:00
Damir Jelić 6e6c474bcb chore(base): Bump the lru crate 2022-09-13 16:03:42 +02:00
Damir Jelić 8ba33f6fd3 chore: Bump vodozemac to a released version 2022-09-13 16:03:42 +02:00
ismailgulek 2591bcbca9 Fix PR remarks 2022-09-13 15:47:26 +03:00
ismailgulek 18a8b2f275 Merge branch 'main' into ismail/logout 2022-09-13 13:01:57 +03:00
ismailgulek f4764bbd8a Fix PR comment 2022-09-13 13:01:38 +03:00
Jonas Platte e46e13d1bf chore: Upgrade Ruma 2022-09-13 08:32:59 +00:00
Kévin Commaille 97995b7bf6 fix(sdk): Re-export qrcode encoding and decoding error types 2022-09-12 19:27:30 +02:00
ismailgulek b4f0438c1a Move processing sync errors into a separate method 2022-09-12 18:40:50 +03:00
ismailgulek 076de488e7 Avoid too much indentation 2022-09-12 17:54:55 +03:00
ismailgulek d0d8e38a1d Merge branch 'main' into ismail/logout 2022-09-12 17:38:24 +03:00
Damir Jelić aac41fc82a refactor(crypto): Remove the forwarding chains
These aren't really useful since they can be easily spoofed by any room
key forwarder.
2022-09-12 16:03:05 +02:00
ismailgulek 268ecea7fe Fix new format error 2022-09-12 16:55:03 +03:00
ismailgulek 24be8a8e82 Update tests 2022-09-12 16:29:46 +03:00
ismailgulek 3cc6fe5dea Fix format errors 2022-09-12 15:35:45 +03:00
Ivan Enderlin d1b2bfcaa4 feat(qrcode): Remove decoding QR code from an image
feat(qrcode): Remove decoding QR code from an image
2022-09-12 13:33:42 +02:00
Ivan Enderlin a0edd2f8d8 test(qrcode): Restore image only for one test. 2022-09-12 12:42:18 +02:00
Ivan Enderlin 478529f230 chore: Update Cargo.lock. 2022-09-12 12:28:31 +02:00
Ivan Enderlin 9411801245 feat(qrcode): Remove decoding QR code from an image.
This patch removes the `decode_image` (default) feature for this
`matrix-sdk-qrcode` crate.

First reason is that `rqrr` panics for some particular QR codes, and
we don't want to panic.

Second reason is that it's not a feature that is used. For Element on
iOS and Android, it's very unlikely that every frame from the camera
will be sent to `matrix-sdk-qrcode` to see if it can be decoded. So
it's obvious that an external library is used to read the bytes from
the QR code, that are then sent to `matrix-sdk-qrcode`. For Element on
Web, it's basically the same argument.

This feature is actually used only in our tests to ensure the
generated QR code is valid, but it sometimes fails due to `rqrr`
(cf. First reason).
2022-09-12 12:15:49 +02:00
ismailgulek 361313fbea Merge branch 'main' into ismail/logout 2022-09-12 13:10:21 +03:00
Ivan Enderlin 2a94f575b8 feat(qrcode): Allow VerificationData to receive a flow ID
feat(qrcode): Allow `VerificationData` to receive a flow ID
2022-09-12 12:10:11 +02:00
Ivan Enderlin f9d09b60d5 chore(crypto): Reduce the size of AnyDecryptedOlmEvent.
`AnyDecryptedOlmEvent::Custom` contains at least 440 bytes, while the
second-largest variant (`RoomKey`) contains at least 0 bytes. Let's
box `Custom` so that the size of `AnyDecryptedOlmEvent` stays low.
2022-09-12 11:57:45 +02:00
ismailgulek 03477afb26 Add initial_device_name and device_id parameters to login method 2022-09-12 12:53:28 +03:00
ismailgulek 6b66a1de56 Introduce did_update_restore_token delegate method 2022-09-12 12:52:27 +03:00
ismailgulek 519a005d16 Introduce is_soft_logout flag on Client and did_receive_auth_error delegate method 2022-09-12 12:51:35 +03:00
Ivan Enderlin 7831e0cd89 chore(crypto): Use to_owned instead of to_string. 2022-09-12 09:49:30 +02:00
Ivan Enderlin 70eeffbbb0 doc(qrcode): Update documentation.
Since we have switched to Vodozemac, those values don't need to be
unpadded base64 anymore.
2022-09-12 09:47:38 +02:00
Jonas Platte 831e802dd0 refactor(bindings): Move some more functions and methods out of UDL 2022-09-09 14:30:23 +02:00
Jonas Platte ab0c144f51 chore: Upgrade UniFFI 2022-09-09 14:30:23 +02:00
Jonas Platte dc05c6e2b8 chore: Silence clippy lint 2022-09-09 12:51:57 +02:00
Jonas Platte 3a6397fdba chore: Update Cargo.lock 2022-09-09 12:51:57 +02:00
Jonas Platte 4a481f09d1 fix(base): Make tokio dev-dependency arch-dependent 2022-09-09 12:51:57 +02:00
Ivan Enderlin fa6745bb60 feat(qrcode): DecodingError::Identifier is no longer useful. 2022-09-08 16:14:30 +02:00
Ivan Enderlin cb21d89229 test(qrcode): Removing decode_invalid_room_id.
A room ID can no longer be invalid, it's just a string representing
either a `EventId` (we can validate that but…) or a `TransactionId`
(which is an opaque string, so it can be anything).
2022-09-08 16:12:21 +02:00
Ivan Enderlin 12b1ec5ef9 feat(crypto): VerificationData takes a flow ID, removing a panic.
This patch updates `QrVerification::new_cross`, by passing a flow ID
as an owned `String` to `VerificationData`, thus removing a panic, and
allowing QR code verification to happen outside a room.
2022-09-08 15:43:26 +02:00
Ivan Enderlin aa1a47831a chore(crypto): Use longer variable names. 2022-09-08 15:43:09 +02:00
Ivan Enderlin cc5034f4b9 feat(qrcode): Allow VerificationData to receive a flow ID.
This patch updates `VerificationData` to receive a flow ID,
represented as an owned `String`, instead of an `OwnedEventId`. Why?
Because QR code verification can happen outside a room. In such
scenario, there is no event ID, but a transaction ID, unified behind
the `matrix_sdk_crypto::FlowId` enum. `VerificationData` doesn't
really care about that details. Proof is that `QrVerificationData`
receives an owned `String`, which is then casted into an
`OwnedEventId` to match this API properly; but at the top, it just
receives a string.

This patch brings also a little bit of clean up while editing code
around.
2022-09-08 15:37:01 +02:00
Jonas Platte 3be8c9585d refactor(sdk): Make SyncSettings Debug repr more compact 2022-09-08 12:13:30 +02:00
Jonas Platte d810fa6883 test(sdk): Enable logging for tests 2022-09-08 12:13:30 +02:00
Jonas Platte a744447bb5 test: Respect RUST_LOG in integration-testing 2022-09-08 12:13:30 +02:00
Damir Jelić 9252e2c7a9 refactor(sdk): Fetch the content using the new account data methods
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-09-07 13:38:42 +02:00
Damir Jelić 282072b6b0 chore(bindings): Fix a typo 2022-09-07 13:38:42 +02:00
Timothy Hobbs e997da0e72 feat(sdk): Make created_dm_room public 2022-09-07 13:38:42 +02:00
Benjamin Kampmann b3f3d0a95e fix(sdk): proper implementation of stripped info preference
Merge pull request #979 from FlixCoder/cleanup

Fixes a problem, where non-stripped information of the room had predecence in all scenarios of the memory-, sled- and indexeddb-store, thought according to the spec, we prefer the full info only until we've received new stripped info (from another invite). This fixes that to be in line with the spec by removing stripped data when we see a full-event and keep stripped info preferred if found (so, after you joined, stripped data is removed and when you knocked or are invited again, it is preferred), without ever deleting full data. It also adds nice unit- and integration tests to ensure this works as intended.
2022-09-06 15:47:14 +02:00
ismailgulek 42767968ec Implement logout method on Client 2022-09-06 15:56:14 +03:00
Benjamin Kampmann 68a8a214ee Update testing/matrix-sdk-integration-testing/assets/ci-start.sh 2022-09-06 12:42:54 +02:00
Jonas Platte 5adec41b6b test: Use a weak password hash for crypto-store testing
This speeds up the crypto-store tests a lot.
2022-09-05 16:58:12 +02:00
Flix b816307ea9 fix: Listen only to events after sending the request 2022-09-05 16:41:55 +02:00
Flix a182578722 fix: Fix rooms being returned in wrong state and having wrong state 2022-09-05 16:41:55 +02:00
Flix 8b5368ff06 chore: Clean up various mini things 2022-09-05 16:41:52 +02:00
Flix a368caf2b0 test: Add StateStore integration test for stripped/non-stripped 2022-09-05 16:33:31 +02:00
Flix df4ee7db4c test: Add test for repeated joining and leaving 2022-09-05 16:33:31 +02:00
Damir Jelić 321e56cff8 fix(examples): Listen to the done event in the emoji verification example
Nowadays all verifications send a done event, so this is the safer
option.
2022-09-05 15:54:45 +02:00
Damir Jelić a640312503 feat(crypto): Add method to format emojis
This patch adds a method to format a list of emojis in a a terminal
friendly way.

This method was borrowed from weechat-matrix but it's also quite useful
in our own emoji verification example.
2022-09-05 15:54:45 +02:00
Damir Jelić b2452ae92c feat(examples): Add a verbosity flag to the emoji example
Since this example prints out messages to stdout, it becomes quite hard
to see when we ask for user input if all the logging is going on.

This patch adds a standard verbosity flag which disables all logging by
default.
2022-09-05 15:54:45 +02:00
Jonas Platte a25b2d4418 refactor(crypto): Clean up cryptostore_integration_tests macro 2022-09-05 15:44:14 +02:00
Jonas Platte 5f6775f47c ci: Only cancel running CI for previous commits on PRs 2022-09-05 15:01:38 +02:00
Jonas Platte c2a222278d ci: Consistently indent job steps 2022-09-05 15:01:38 +02:00
Jonas Platte 567b230cb7 ci: Cache xtask binary 2022-09-05 15:01:38 +02:00
Jonas Platte 8535c16bc8 ci: Don't require fmt to run before typo check and clippy 2022-09-05 15:01:38 +02:00
Jonas Platte d009d0475e ci: Move appservice and style back into ci workflow
Required for job dependencies.
2022-09-05 15:01:38 +02:00
Jonas Platte 6b8cf3c02a chore: Remove pre-commit configuration and CI job
It was not really being used.
2022-09-05 15:01:38 +02:00
Jonas Platte edfc0cbe20 refactor(sdk): Deprecate ClientBuilder::user_id 2022-09-05 12:43:21 +02:00
Jonas Platte 91186d8a25 doc(sdk): Replace deprecated method in doctest 2022-09-05 11:55:49 +02:00
Jonas Platte 090d67b6ef refactor(sdk): Move module-level documentation inside the module files 2022-09-05 11:55:49 +02:00
Jonas Platte fd4957f533 test(sdk): Address commented-out code in a test 2022-09-05 11:55:49 +02:00
Jonas Platte 08760bd4c0 refactor(sdk): Make base_client field of Client private 2022-09-05 11:55:49 +02:00
Jonas Platte b769827313 refactor(sdk)!: Move media methods from Client to a new type 2022-09-05 11:55:49 +02:00
Jonas Platte 79b5854c83 feat(sdk): Add request_config method to Client 2022-09-05 11:55:49 +02:00
Jonas Platte 38324f6c60 refactor(sdk): Use Client::send in Client::upload 2022-09-05 11:55:49 +02:00
Jonas Platte 33bce0b18d refactor(sdk): Move RoomMember into room module 2022-09-05 11:55:49 +02:00
Jonas Platte 245ecea263 feat(sdk): Add support for AnySyncTimelineEvent in event handlers 2022-09-05 11:55:17 +02:00
Jonas Platte d4ac1bffd0 refactor(sdk): Rename EventKind to HandlerKind
It's somewhat different as MessageLike, OriginalMessageLike and
RedactedMessageLike are not three distinct event kinds in Ruma.
2022-09-05 11:55:17 +02:00
Jonas Platte d6af63e37b refactor(sdk): Run event handlers for the same event concurrently 2022-09-05 11:55:17 +02:00
Jonas Platte 57dde2c4d3 refactor(sdk): Avoid duplicate work and fix event handler call order
Previously, when both a possibly-redacted timeline event handler and a
non-redacted timeline timeline event handler would apply to multiple
events in a sync response, they would individually run for every event
in order. With this change, they will instead both be called
for one event before the next is processed.
2022-09-05 11:55:17 +02:00
Kévin Commaille 6f3813a65f Re-export vodozemac errors in a separate module 2022-09-05 11:44:42 +02:00
Kévin Commaille 433f75ae57 Remove dead error variants 2022-09-05 11:44:42 +02:00
Kévin Commaille 8f0fb08fe7 feat(sdk): Re-export matrix-sdk-crypto errors 2022-09-05 11:44:42 +02:00
Jonas Platte 4f6ff5c0d3 refactor(sdk): Make use of new Default impls from Ruma 2022-09-02 15:03:00 +02:00
Jonas Platte c4d46f233e chore: Upgrade ruma 2022-09-02 15:03:00 +02:00
Damir Jelić faf0ce5007 chore(examples): Remove some empty useless attributes 2022-09-02 14:54:33 +02:00
Damir Jelić c5e6178b70 refactor(examples): Add proxy support to the emoji verification example 2022-09-02 14:54:33 +02:00
Jonas Platte bf9f431e22 refactor(sdk): Use new account_data method internally 2022-09-02 14:09:17 +02:00
Damir Jelić 4b673cfe9c chore: Fix a bunch of clippy warnings 2022-09-02 11:39:06 +02:00
Benjamin Kampmann 0079f1d569 Merge pull request #977 from zecakeh/fix-examples-autojoin
fix(examples): Fix examples that accept invites
2022-09-02 11:14:40 +02:00
Damir Jelić a71292cb16 chore(crypto): Remove a bunch of unnecessary allow deprecated attributes 2022-09-01 17:08:24 +02:00
Damir Jelić cc813b049e fix(crypto): Set the correct algorithm when exporting a room key 2022-09-01 17:08:24 +02:00
Damir Jelić 1b06d8ca51 refactor(crypto): Start using the customized room key request event type 2022-09-01 17:08:24 +02:00
Damir Jelić 4338d5e534 refactor(crypto): Add a customized room key request event type 2022-09-01 17:08:24 +02:00
Jonas Platte 4c98dfb42a feat(bindings): Enable socks proxy support in matrix-sdk-ffi 2022-09-01 15:07:15 +02:00
Jonas Platte dacaef3ddd fix(bindings): Reduce scope of RwLock read lock
Fixes a clippy lint.
2022-09-01 13:40:47 +02:00
Jonas Platte e4267cc4fd refactor(sdk)! Make upload take &[u8] instead of impl Read
The use of `io::Read` wasn't helping since we had to buffer the whole
file in memory anyways, and we are unlikely to get around that in the
near future.
2022-09-01 13:40:47 +02:00
Jonas Platte 16ac69a967 chore: Simplify integration testing macro 2022-09-01 13:11:05 +02:00
Ivan Enderlin 193da88320 feat(crypto): Rename verified and deleted to is_*
feat(crypto): Rename `verified` and `deleted` to `is_*`
2022-09-01 10:50:05 +02:00
Ivan Enderlin 0d6a19e388 chore: verified has been renamed is_verified. 2022-09-01 10:35:51 +02:00
Flix f49e9be905 feat: Expose client of rooms for extensions 2022-09-01 10:01:18 +02:00
Jonas Platte a954518d73 ci: Stop ignoring .lock files in typos config explicitly
Typos now ignores .lock files automatically without any configuration.
2022-09-01 09:33:40 +02:00
Damir Jelić 16d9ed230a chore(examples): Use automatic links for some URLs 2022-08-31 18:43:47 +02:00
Ivan Enderlin 82b647a888 doc(crypto): Fix typos in the documentation
doc(crypto): Fix typos in the documentation
2022-08-31 17:26:56 +02:00
Ivan Enderlin 2e74983c79 chore: Fix other is_verified. 2022-08-31 17:16:05 +02:00
Ivan Enderlin ffebc7c313 doc(crypto): Fix typos in the documentation 2022-08-31 17:09:33 +02:00
Ivan Enderlin 7d1b60a3b1 doc(crypto): Fix a link. 2022-08-31 16:52:00 +02:00
Ivan Enderlin 53c5158eca doc(crypto): Update link to `is_verified. 2022-08-31 16:45:52 +02:00
Ivan Enderlin 3eab9ca8e5 feat(crypto): Rename verified and deleted to is_*. 2022-08-31 16:44:59 +02:00
Doug 36b41ac1c6 chore(bindings): Replace failing test. 2022-08-31 16:22:59 +02:00
Doug 8a8cc5f230 chore(bindings): Use Swift package for tests. 2022-08-31 16:22:59 +02:00
Jonas Platte 96384d9447 feat(bindings): Add account data interaction to sdk-ffi
Co-authored-by: Doug <douglase@element.io>
2022-08-31 14:40:23 +02:00
Stefan Ceriu bb04a1e041 chore(bindings): Fix Xcode project after sdk-ffi namespace change 2022-08-31 10:51:04 +00:00
Jonas Platte d416e64a7e refactor(sdk): Return only content from Account::account_data[_raw]
Since global account data events only consist of the type that is known
to the user anyways, and the content.
2022-08-31 12:19:35 +02:00
Jonas Platte aedf807025 doc(sdk): Add an example for set_account_data 2022-08-31 12:19:35 +02:00
Jonas Platte 77afa26217 feat(sdk): Add account_data[_raw] accessors to Account 2022-08-31 12:19:35 +02:00
Jonas Platte f1a03ececd feat(sdk): Add public set_account_data[_raw] to Account 2022-08-31 12:19:35 +02:00
Jonas Platte f8502720c3 fix(bindings): Pass library file to uniffi-bindgen
… so that functions bridged via #[uniffi::export] are included in the
generated Swift API.
2022-08-31 11:52:37 +02:00
Jonas Platte 84f9414aa4 refactor(bindings): Simplify debug build script 2022-08-31 11:52:37 +02:00
Jonas Platte 9cfddc4c65 refactor(bindings): Update namespace name for matrix-sdk-ffi 2022-08-31 11:52:37 +02:00
Damir Jelić 01f8ed10aa feat(examples): Add an example that lets you create rooms 2022-08-31 11:51:50 +02:00
Damir Jelić 6bb9a7a7d7 docs(crypto): Improve some docs about the forwarded curve chains
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2022-08-30 17:14:29 +02:00
Damir Jelić 75fb3b81a7 fix(crypto): Fix the deserialization of exported inbound group sessions 2022-08-30 17:14:29 +02:00
Damir Jelić 504ad39c27 fix(crypto): Fix the deserialization of inbound group group_sessions
Inbound group sessions would fail to be deserialized if there was a
forwarding curve chain, this patch fixes it so we go through base64 when
we deserialize this field.
2022-08-30 17:14:29 +02:00
Benjamin Kampmann d20db7c7c1 Merge pull request #981 from Hywan/fix-crypto-js-npm-publish
chore(crypto-js): Add `npm run pack`
2022-08-30 08:50:39 +02:00
Ivan Enderlin 1bfcc52a1f chore(crypto-js): Add npm run pack.
This patch introduces a new `pack` NPM script, which runs `wasm-pack
pack` behind the scene.

This patch modifies the `publish` NPM script to run the `pack` script
as a pre-script (so… in the `prepublish` script).

Finally, this patch no longer uses `$npm_execpath` as it doesn't work
on Windows. It should be `%npm_execpath%`. It's not obvious to make
scripts interoperable, so we will stick with `npm` for now.
2022-08-29 14:22:39 +02:00
Damir Jelić a8362e389e Document the receive_supported_keys method in the gossiping module 2022-08-29 10:21:04 +02:00
Damir Jelić fe35e7c9fa ci: Test the experimental-algorithms feature of the crypto crate 2022-08-29 10:21:04 +02:00
Damir Jelić 5768188da8 Fix some clippy warnings 2022-08-29 10:21:04 +02:00
Damir Jelić 1e451a92e0 Add some missing docs to the Olm Session 2022-08-29 10:21:04 +02:00
Damir Jelić 12c1b80bc9 Remove a dead code allowed attribute 2022-08-29 10:21:04 +02:00
Damir Jelić 7fda431d5f Remove the usage of the Ruma EventEncryptionAlgorithm 2022-08-29 10:21:04 +02:00
Damir Jelić 337fbb591d Put the new megolm algorithm behind the experimental feature flag 2022-08-29 10:21:04 +02:00
Damir Jelić 8cca01369a Put the new olm algorithm behind a feature flag 2022-08-29 10:21:04 +02:00
Damir Jelić 8cdc609876 Move the EventEncryptionAlgorithm into a more logical place 2022-08-29 10:21:04 +02:00
Damir Jelić 0673ad315f Add support for megolm.v2 forwarded keys 2022-08-29 10:21:04 +02:00
Damir Jelić 3aaf70cb5a Support the new algorithms in the gossiping tests 2022-08-29 10:21:04 +02:00
Damir Jelić b00e963a21 Add support for the new algorithms in the bindings 2022-08-29 10:21:04 +02:00
Damir Jelić 7d98d87c5a Enable the new olm/megolm algorithms 2022-08-29 10:21:04 +02:00
Damir Jelić fb840f73a3 Add support for the m.megolm.v2.aes-sha2 room key content 2022-08-29 10:21:04 +02:00
Damir Jelić e935f59039 Add support for the m.megolm.v2.aes-sha2 algorithm 2022-08-29 10:21:04 +02:00
Damir Jelić 3df6797419 Add support for the m.olm.v1.curve25519-aes-sha2 algorithm 2022-08-29 10:21:04 +02:00
Damir Jelić eaf1f27831 refactor(crypto): Use our own enum for the encryption algorithms 2022-08-29 10:21:04 +02:00
Damir Jelić 748eff40f0 docs(crypto): Improve some docs around event trust states
Co-authored-by: Denis Kasak <dkasak@termina.org.uk>
2022-08-29 09:50:38 +02:00
Damir Jelić 0084117de9 docs(crypto): Fix some spelling and improve a couple of docs
Co-authored-by: Ivan Enderlin <ivan@mnt.io>
2022-08-29 09:50:38 +02:00
Damir Jelić a4af5bf4e1 fix(crypto): Improve the code checking if a decrypted event is trusted
This now takes into account if the room key was imported, i.e. received
as a forward. We also do the check of the Ed25519 key now.
2022-08-29 09:50:38 +02:00
Damir Jelić 27d4228269 fix(crypto): Check the Ed25519 key when we receive an Olm encrypted event 2022-08-29 09:50:38 +02:00
Kévin Commaille dc39c8d96b fix(examples): Fix examples that accept invites
Spawn a task for accepting the invite otherwise the call never returns and sync stops.
2022-08-26 18:15:56 +02:00
Jonas Platte d427632230 chore: Add more backticks in comments 2022-08-25 18:09:13 +02:00
Jonas Platte 6ddc8ba36f refactor!: Rename [Sync]RoomEvent to [Sync]TimelineEvent 2022-08-25 18:09:13 +02:00
Jonas Platte 4be2f3aa04 chore: Upgrade ruma 2022-08-25 18:09:13 +02:00
Jonas Platte f6c404cb32 feat(sdk): Allow event enums to be used as the first event handler argument 2022-08-25 17:19:44 +02:00
Jonas Platte 20a6a16152 refactor(sdk): Split EventHandlerMap into separate maps
One for room-specific event handlers, one for non-room-specific ones.
2022-08-25 17:19:44 +02:00
Jonas Platte 5a1853b0d5 refactor(sdk): Split event_handler module into more files 2022-08-25 17:19:44 +02:00
Jonas Platte 9183f5d4ef refactor(sdk): Move event handler fields from Client into a new struct 2022-08-25 17:19:44 +02:00
Ivan Enderlin 973833c643 feat(crypto-js): Add store configuration to the OlmMachine constructor
feat(crypto-js): Add store configuration to the `OlmMachine` constructor
2022-08-25 11:37:45 +02:00
Ivan Enderlin ce4f7e2254 Merge branch 'main' into feat-crypto-js-store 2022-08-25 11:22:22 +02:00
Jonas Platte 0018749e15 feat(sdk): Allow Raw<_> to be used as the first event handler argument 2022-08-24 18:35:09 +02:00
Ivan Enderlin 9c414bbe9f feat(crypto-js): Implement OlmMachine.sign and .cross_signing_status
feat(crypto-js): Implement `OlmMachine.sign` and `.cross_signing_status`
2022-08-24 09:48:03 +02:00
Jonas Platte 0b8462423a chore: Reduce indirect dependencies of examples 2022-08-23 18:30:44 +02:00
Jonas Platte 2313c099fa chore(bindings): Replace parking_lot RwLock by std RwLock 2022-08-23 18:30:44 +02:00
Ivan Enderlin 85795a92b6 chore(crypto-js): Make Clippy happy. 2022-08-23 17:38:32 +02:00
Ivan Enderlin ef8207fbaa chore(crypto-js): Make Clippy happy. 2022-08-23 17:06:07 +02:00
Jonas Platte f83292fc75 refactor: Start using UniFFI proc-macro frontend 2022-08-23 16:47:08 +02:00
Jonas Platte 5faed2e635 Upgrade UniFFI 2022-08-23 16:47:08 +02:00
Ivan Enderlin 9722a4c415 chore(crypto-js): Use IndexeddbCryptoStore only for wasm32. 2022-08-23 16:46:09 +02:00
Ivan Enderlin 432449b009 chore(crypto-js): Make cargo fmt happy. 2022-08-23 15:08:51 +02:00
Ivan Enderlin 04634d5f39 chore(crypto-js): Fix a typo in an error message. 2022-08-23 15:04:50 +02:00
Ivan Enderlin ef56f76978 doc(crypto-js): Fix a typo. 2022-08-23 15:03:26 +02:00
Ivan Enderlin 527c727e4a test(crypto-js): Improve test cases. 2022-08-23 15:00:39 +02:00
Ivan Enderlin d947448c64 test(crypto-js): Add more test case when store is not configured correctly. 2022-08-23 15:00:39 +02:00
Ivan Enderlin d497883669 doc(crypto-js): Add more docmuentation and more error messages. 2022-08-23 15:00:39 +02:00
Ivan Enderlin 7f07ac52ef feat(crypto-js): Add store configuration to the OlmMachine constructor.
The `OlmMachine` constructor now has 2 more optional arguments:
`store_name` and `store_passphrase`, to use Indexed DB as the backend
to store the Olm machine keys, rather than having an in-memory Olm
machine.

Node.js is used to test our binding. Indexed DB is absent of
Node.js. So, firstly, we are using the `fake-indexeddb` JavaScript
library to mimic the same API inside Node.js. And, secondly, we use
our own fork of the `indexed_db_futures` Rust crate to provide add
support for Node.js too ([PR is
here](https://github.com/Alorel/rust-indexed-db/pull/11)). It
basically looks in the Node.js global environment if the `indexedDB`
getter is present, just like it is already done for `Window` on any
browser, or `WorkerGlobalScope` for workers.
2022-08-23 15:00:33 +02:00
Jonas Platte 9462061a5a chore: Upgrade ruma 2022-08-23 14:28:21 +02:00
Ivan Enderlin d508237078 chore(crypto-js): Add a release workflow for crypto-js
chore(crypto-js): Add a release workflow for `crypto-js`
2022-08-23 10:49:29 +02:00
Ivan Enderlin 331f8b381f doc(crypto-js): Update documentation for the release workflow. 2022-08-22 15:10:01 +02:00
Ivan Enderlin 2481618608 chore(crypto-js): Create the Github release with the NPM package attached.
This patch updates the `publish` NPM script to explicitly run
`wasm-pack pack` to create the NPM package in the `pkg/`
directory. This patch also updates the Github Action workflow for the
`matrix-sdk-crypto-js` release, to create a new Github Release, with
the NPM package attached as an asset file.
2022-08-22 14:37:09 +02:00
Jonas Platte dd78a8cecd refactor(sdk)!: Make room type constructors private
… and avoid unnecessary clones before calling them.
2022-08-22 13:16:05 +02:00
Ivan Enderlin 39077b185b chore(crypto-js): Add a release workflow for crypto-js.
This patch adds a new `npm run publish` script that:

1. Run `npm run prepublish` (which runs the `build` and `test` scripts),
2. Run `wasm-pack publish`.

Note 1: The `prepublish` script is using the `$npm_execpath`
environment variable instead of just “`npm`”, so that if someone is
using `yarn` or another JavaScript package manager, it _should_ work
(not tested yet).

Note 2: `wasm-pack publish` is run without running `wasm-pack login`
before that. _But_ we are updating the registry URL in the NPM
configuration file in the Github Action workflow, see below.

This patch then creates a new Github Action workflow that is triggered
when a new tag of the form `matrix-sdk-crypto-js-v[0_9]+.*` is
pushed. This workflow runs `npm run publish` basically, but before
that, it updates the NPM configuration file by setting a value for
`//registry.npmjs.org/:_authToken`. Thus, running `wasm-pack login` is
not necessary.
2022-08-22 12:14:44 +02:00
Benjamin Kampmann 68107e6285 Merge pull request #963 from gnunicorn/ben-update-ruma
chore: Update ruma
2022-08-18 16:13:15 +02:00
Benjamin Kampmann 549c829000 chore: Update ruma 2022-08-18 15:52:08 +02:00
Jonas Platte 3581d83389 chore: Upgrade Ruma 2022-08-17 10:15:05 +02:00
Jonas Platte 260b604615 refactor(sdk)!: Split strongly-typed state event functions into two
One for empty state keys under the existing name, one for non-empty
state keys with a `_for_key` suffix.
2022-08-17 10:15:05 +02:00
Ivan Enderlin a1dca23c3c Merge pull request #958 from Hywan/feat-crypto-js-attachment 2022-08-17 10:12:04 +02:00
Benjamin Kampmann 9fd5a8b3b1 Merge pull request #960 from matrix-org/poljar/delete-device-log-improvement
Improve the log line when we think our device has been deleted
2022-08-17 09:46:26 +02:00
Damir Jelić 180822dd18 chore(crypto): Improve the message when we think that we have been deleted 2022-08-16 18:32:14 +02:00
Damir Jelić 20477ab7da chore(crypto): Log our identity keys when we think our device has been deleted 2022-08-16 18:31:30 +02:00
Damir Jelić 06f39696d3 chore(crypto): Improve some logs in the Olm message handling code 2022-08-16 16:18:50 +02:00
Damir Jelić 6f5443f8a0 chore(crypto): Log some info when we try to create an Olm session from a pre-key message 2022-08-16 16:18:50 +02:00
Ivan Enderlin 45a66f3321 doc(crypto-nodejs): Fix a typo. 2022-08-16 16:06:04 +02:00
Ivan Enderlin 7f35691236 feat(crypto-js): Implemented the Attachment API.
Implement the `Attachment.encrypt` and `Attachment.decrypt` methods,
along with its `EncryptedAttachment` companion.

The trick is to avoid copies as much as possible. Instead of dealing
with `Uint8Array` as I've initially done, `&[u8]` and `Vec<u8>` is
passed instead. `wasm-bindgen` is smart enough to do as few copies as
possible (from JavaScript to Wasm's memory is required, and we don't
want to do more), while typing everything as `Uint8Array`.

`EncryptedAttachment.encryptedData` returns a copy of the data
everytime it is called, and that's the last copy I'm not happy with.
2022-08-16 15:56:36 +02:00
Benjamin Kampmann e8d51a4cba Merge pull request #956 from gnunicorn/ben-fix-encode-key
fix(sled): Remove unused `from` on EncodeUnchecked
2022-08-16 15:51:36 +02:00
Benjamin Kampmann 7feffec9c0 fix(sled): feature-gate EncodeUnchecked::from 2022-08-16 15:18:46 +02:00
Ivan Enderlin 8e74834342 doc(crypto-js): Add missing module documentation. 2022-08-16 12:31:19 +02:00
Ivan Enderlin 070637b0ef chore(crypto-js): Fix style. 2022-08-16 12:21:02 +02:00
Ivan Enderlin 2dffe03c8d feat(crypto-js): Implement OlmMachine.sign. 2022-08-16 12:16:01 +02:00
Ivan Enderlin 3f0509e7b1 fix(crypto-js): Add missing Debug impl. 2022-08-16 12:16:01 +02:00
Ivan Enderlin 2275643ea0 chore(crypto-js): Move Vodozemac types into their own Rust module. 2022-08-16 12:16:01 +02:00
Ivan Enderlin 0fa6ff6955 feat(crypto-js): Add DeviceKeyId, DeviceKeyAlgorith and DeviceKeyAlgorithmName. 2022-08-16 12:16:01 +02:00
Ivan Enderlin 997a6ed0ad feat(crypto-js): Implement OlmMachine.cross_signing_status. 2022-08-16 12:16:01 +02:00
Ivan Enderlin fd220f197b doc: Add link to online documentation for crypto-js and crypto-nodejs
doc: Add link to online documentation for crypto-js and crypto-nodejs
2022-08-16 10:21:39 +02:00
Ivan Enderlin 655e62814b doc(crypto-nodejs) Add link to online documentation. 2022-08-16 10:06:22 +02:00
Ivan Enderlin c2468b2f2e doc(crypto-js) Add link to online documentation. 2022-08-16 10:04:54 +02:00
Benjamin Kampmann e0ae4f60e3 docs(examples): update instructions for wasm_command_bot example
Merge pull request #952 from chrisguida/chrisguida/wasm-bot-readme -
2022-08-16 09:48:33 +02:00
Chris Guida 2beb13cc3e update instructions for wasm_command_bot example 2022-08-15 17:56:54 -05:00
Damir Jelić 78dcff9259 chore(crypto): Fix an indentation issue
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-08-15 11:39:43 +02:00
Damir Jelić 0bd58a7741 refactor(crypto): Simplify the Curve25519 check when fetching a device
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-08-15 11:39:43 +02:00
Damir Jelić 405c3938b7 refactor(crypto): Use the Curve25519 key type in more places 2022-08-15 11:39:43 +02:00
Damir Jelić bd5ea8b0f7 fix(crypto): Fix some error messages 2022-08-15 11:39:43 +02:00
Jonas Platte 24c042e974 refactor(base): Deserialize events at the expected type
… rather than deserializing at an enum type and taking the same branch
for getting a different enum variant¹ as failed deserialization.

¹ which should be impossible anyways
2022-08-15 11:05:22 +02:00
Jonas Platte d236cde0c7 refactor(base): Use StateStoreExt in a few places 2022-08-15 11:05:22 +02:00
Jonas Platte d2f39bc18f feat(base): Add StateStoreExt for statically-typed event retrieval 2022-08-15 11:05:22 +02:00
Jonas Platte 96165f5602 chore(bindings): Use ? operator instead of and_then chaining 2022-08-15 11:05:22 +02:00
Damir Jelić 4a13b8d207 ci: Ignore thead when checking for spelling 2022-08-14 10:20:28 +02:00
Damir Jelić c9c09adb38 chore: Fix some newly detected typos 2022-08-14 10:20:28 +02:00
Jonas Platte aedbbcdde7 fix(sdk)!: Remove SyncEvent implementation for InitialStateEvent
Initial state events aren't actually received through sync, they're sent from
the client to the server when creating a room.

This change makes it impossible to register event handlers for initial state
events. Previously, this was allowed, but the event handler was never called.
2022-08-13 20:21:28 +02:00
Jonas Platte 97f37acb4a chore: Reduce indirect dependencies of sled-state-inspector 2022-08-13 13:08:11 +02:00
Damir Jelić 0b5bfeadea chore(crypto): Add a missing semicolon
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-08-13 09:58:57 +02:00
Damir Jelić 6d60acfff4 chore(common): Fix a clippy warning 2022-08-13 09:58:57 +02:00
Damir Jelić 3d56af442d refactor(crypto): Utilize the decrypted Olm event type more
This patch adds some more stronger guarantees that received room key
events and other similarly security critical event types are only
handled if they have been received over a secure Olm channel.
2022-08-13 09:58:57 +02:00
Damir Jelić ae18b01c25 refactor(crypto): Use the SigningKeys collection for inbound group sessions 2022-08-11 16:43:42 +02:00
Damir Jelić 4692a12cd7 refactor(crypto): Create a custom collection type for signed keys 2022-08-11 16:43:42 +02:00
Jonas Platte 344309c1bf chore: Track Cargo.lock to make binding staticlibs reproducible 2022-08-11 13:32:32 +02:00
Jonas Platte 87094a9111 chore: Document dependency upgrade issues 2022-08-11 13:16:10 +02:00
Jonas Platte adf3f9d434 chore(appservice): Upgrade serde_yaml 2022-08-11 13:15:57 +02:00
Jonas Platte 158bd24b40 chore: Bump pprof dependency 2022-08-11 13:09:09 +02:00
Jonas Platte 8e368d86b7 chore: Use the latest git version of UniFFI 2022-08-11 12:56:17 +02:00
Jonas Platte 0fe714df86 chore: Upgrade sled-state-inspector dependencies 2022-08-11 11:59:50 +02:00
Jonas Platte 936f0371de chore: Use once_cell instead of lazy_static in integration test crate 2022-08-11 10:45:04 +02:00
Jonas Platte ecc800a319 chore: Sort dependencies of integration test crate 2022-08-11 10:32:12 +02:00
docweirdo febfcf9796 feat(sdk): Make room actions return typed room objects
… by waiting for the corresponding event confirming the action. Affects:

* Creating a room
* (Re-)Joining a room
* Leaving a room
* Accepting an invitation
* Rejecting an invitation
2022-08-10 17:32:38 +02:00
Damir Jelić 3f0a68082a feat(crypto): Add a setting to only send room keys to trusted devices 2022-08-10 13:31:15 +02:00
Damir Jelić 18861bb595 refactor(crypto): Add a dedicated error for inbound group session exporting 2022-08-10 13:28:54 +02:00
Damir Jelić 329d461a2f feat(crypto): Add customized event type for the forwarded room key 2022-08-10 13:28:54 +02:00
Jonas Platte 4914c595e9 fix(sdk): Further relax event / notification handler bounds on WASM
This allows capturing non-`Send` / -`Sync` values in handler closures.
2022-08-10 12:54:36 +02:00
Jonas Platte ad80839ffd fix(sdk): Make event handler futures non-Send on wasm 2022-08-09 14:25:57 +02:00
Jonas Platte 0701561e45 feat(common): Add SendOutsideWasm, SyncOutsideWasm 2022-08-09 14:25:57 +02:00
Jonas Platte 25030780b0 test(sdk): Run event handler tests on wasm 2022-08-09 14:25:57 +02:00
Jonas Platte 327a404d60 test(sdk): Move test client creation into separate module 2022-08-09 14:25:57 +02:00
Damir Jelić 06e096f6cc fix(examples): Fix the in-room emoji verification example 2022-08-09 14:04:11 +02:00
Damir Jelić a025163dae test(crypto): Test that we correctly preserve relations in an encryption cycle 2022-08-09 14:04:11 +02:00
Damir Jelić 19fcff56de chore(crypto): Make the relation copying code when decrypting a bit simpler 2022-08-09 14:04:11 +02:00
Damir Jelić edf81fb325 fix(crypto): Correctly copy over the relation when encrypting 2022-08-09 14:04:11 +02:00
Damir Jelić 08338acac2 fix(crypto): Fix the deserialization of relations 2022-08-09 14:04:11 +02:00
Kévin Commaille 52e70f1955 refactor(sdk): Don't require the whole session for sending
Allow to send requests when only the access token is available.

Remove the unreachable UserIdRequired error.
2022-08-09 12:26:33 +02:00
Jonas Platte e788ec6b07 ci: Only test one node.js version on macOS 2022-08-08 18:06:54 +02:00
Jonas Platte d3d108deb8 chore: Add missing copyright headers 2022-08-08 16:28:48 +02:00
Jonas Platte bd65d9b7a6 doc(sdk): Remove usage of deprecated function in doctest 2022-08-05 16:16:41 +02:00
Jonas Platte 230bb67763 refactor(sdk): Remove Clone requirement on event handlers 2022-08-05 16:16:41 +02:00
Jonas Platte 10a37f6d51 chore(sdk): Add EventHandlerDropGuard
It isn't used for now, but will be soon.
2022-08-05 16:16:41 +02:00
Jonas Platte 5156fb7dd4 refactor(sdk): Move add_event_handler_impl to event_handler module 2022-08-05 16:16:41 +02:00
Jonas Platte 3b03ad804f refactor(sdk)!: Swap the async lock around event handlers for a sync one 2022-08-05 16:16:41 +02:00
Damir Jelić 603176f521 chore(crypto): Bump vodozemac
Vodozemac got some new knobs to twiddle with. This patch updates
vodozemac and sets the knobs to use the olm/megolm v1 supported
encryption schemes.
2022-08-05 14:06:42 +02:00
Jonas Platte dfec17e6af chore: Disable testing of crates without tests
This reduces the amount of "running 0 tests" spam when testing the whole
workspace and makes testing a little faster overall.
2022-08-05 11:10:31 +02:00
Kévin Commaille 7623f93bb3 fix(sdk)!: Make set_homeserver private
A user shouldn't need to change the homeserver after creating the client.
2022-08-05 09:55:55 +02:00
Jonas Platte a60620306c ci: Update tarpaulin.toml 2022-08-04 23:43:49 +02:00
Jonas Platte 1fea48359d ci: Checkout head ref instead of merge commit for coverage
… this *should* fix bogus coverage change reporting.
2022-08-04 23:43:49 +02:00
Jonas Platte 054dfa98a0 ci: Upgrade checkout action 2022-08-04 23:43:49 +02:00
Damir Jelić 593d4e6062 perf(crypto): Use an RwLock for the OutboundGroupSession 2022-08-04 17:36:03 +02:00
Jonas Platte 5a94ba7b80 refactor(sled)!: Align open_with_database with its documentation
It now takes a passphrase instead of a store cipher as the second argument.
2022-08-04 17:04:31 +02:00
Jonas Platte e1b7f3be05 refactor(sled): Simplify test code 2022-08-04 17:04:31 +02:00
Jonas Platte 6bed51f016 refactor(indexeddb)!: Use &str for name in public API 2022-08-04 17:04:31 +02:00
Jonas Platte 4db162b8a2 chore(indexeddb): Clean up Result usage 2022-08-04 17:04:31 +02:00
Jonas Platte 0b1bdd66f9 refactor(indexeddb): Export error types 2022-08-04 17:04:31 +02:00
Jonas Platte a4f3c3a070 refactor!: Give sled / indexeddb types unique names 2022-08-04 17:04:31 +02:00
Jonas Platte df7895d2c6 chore(indexeddb): Rename cryptostore => crypto_store
… for consistency with state_store.
2022-08-04 17:04:31 +02:00
Jonas Platte 694c36d741 chore(sled): Rename cryptostore => crypto_store
… for consistency with state_store.
2022-08-04 17:04:31 +02:00
Benjamin Kampmann b5329f99f1 Merge pull request #903 from gnunicorn/ben-getting-started-example
Improving examples
2022-08-04 15:41:26 +02:00
Damir Jelić f055e939e7 refactor(crypto): Use the vodozemac method to decide if a session is better
This patch delegates the decision making if a session is better to
vodozemac. Vodozemac has better insights if a group session can be
considered to be better.
2022-08-04 15:09:09 +02:00
Benjamin Kampmann c4c7c2bb23 docs: remove unused import and fix style of custom events example 2022-08-04 15:02:39 +02:00
Benjamin Kampmann efc0556124 doc: follow naming convention and use generated types from ruma 2022-08-04 13:31:16 +02:00
Benjamin Kampmann 9d588f7e00 doc: add example for sending and reacting on custom events 2022-08-04 13:13:48 +02:00
Benjamin Kampmann 323974fe4c Merge remote-tracking branch 'origin/main' into ben-getting-started-example 2022-08-04 12:22:42 +02:00
Benjamin Kampmann 3bdb2f22ea build(crypto-js): Pin yarg-parser to 21.0.1 to prevent upgrade bug
We are effected by https://github.com/yargs/yargs-parser/issues/452
through the transient dependency of jest on yargs
2022-08-04 12:21:18 +02:00
Damir Jelić ddf8577c84 refactor(crypto): Start using our own event types when we encrypt 2022-08-04 11:16:02 +02:00
Benjamin Kampmann 90c2dcdbc0 style: fix clippy lints 2022-08-04 11:02:28 +02:00
Benjamin Kampmann fe4bc0dc75 doc: fix docs of getting started bot 2022-08-03 17:07:59 +02:00
Benjamin Kampmann 69c8cdf304 Revert "fix: limit indexeddb features to target arch to stop clippy from complaining"
This reverts commit d54612b6e2.
2022-08-03 16:40:17 +02:00
Benjamin Kampmann daf92d7745 Merge remote-tracking branch 'origin/main' into ben-getting-started-example 2022-08-03 16:39:50 +02:00
Benjamin Kampmann ccbd9e5712 chore: rename Readme.md to README.md 2022-08-03 16:39:14 +02:00
Jonas Platte 2430d7f267 chore: Remove unneeded docsrs Cargo features 2022-08-03 15:33:53 +02:00
Damir Jelić 3ca3016539 refactor(crypto): Use the curve 25519 key type for inbound group sessions 2022-08-03 15:28:24 +02:00
Jonas Platte bbbd7942b0 fix(indexeddb): Export MigrationConflictStrategy 2022-08-03 15:17:05 +02:00
Jonas Platte 1a5953e01e chore(indexeddb): Appease clippy 2022-08-03 15:17:05 +02:00
Jonas Platte 3a19d8e5bf chore(indexeddb): Reduce target_arch feature gates
This allows smoother development as a regular 'cargo check' will
validate most of the code. It also makes rust-analyzer work for the
crate without any configuration.
2022-08-03 15:17:05 +02:00
Jonas Platte 0b7d0aa780 chore(common): Make timeout tests deterministic
… as well as simpler, and faster.
2022-08-03 14:54:43 +02:00
Jonas Platte 82f4e57a2c feat(sled): Print a clear error message when attempting to build on wasm 2022-08-03 14:48:45 +02:00
Jonas Platte 9714ce9edf refactor(sdk): Check permissible feature configuration in build script
… instead of through `compile_error!` invocations. This helps avoid
unhelpful errors from the unsupported feature configuration by aborting
compilation earlier.
2022-08-03 14:48:45 +02:00
Benjamin Kampmann 9538596fbb doc: Add getting-started example autojoin & command bot with plenty of source docs 2022-08-03 13:05:25 +02:00
Benjamin Kampmann 35be128139 docs: add Readme to examples root 2022-08-03 12:06:50 +02:00
Benjamin Kampmann d54612b6e2 fix: limit indexeddb features to target arch to stop clippy from complaining 2022-08-03 12:00:54 +02:00
Benjamin Kampmann a1bf53a331 Merge remote-tracking branch 'origin/main' into ben-getting-started-example 2022-08-03 11:39:50 +02:00
Kévin Commaille 9064e7b02d feat(sdk): Add support for refresh tokens 2022-08-03 10:42:28 +02:00
Damir Jelić ae261c2091 refactor(crypto): Refactor the key sharing tests a bit 2022-08-02 16:48:05 +02:00
Damir Jelić 968792ea00 refactor(crypto): Split out the forwarded room key accepting method 2022-08-02 16:48:05 +02:00
Benjamin Kampmann 42c88e840f Merge pull request #905 from gnunicorn/ben-fix-integration-test-coverage
ci: add backend server for integration test with tarpaulin
2022-08-02 16:14:26 +02:00
Benjamin Kampmann 38a71972e5 ci: add backend server for integration test with tarpaulin 2022-08-02 15:42:45 +02:00
Benjamin Kampmann d8caaed1ce docs: only document default workspace members, not all 2022-08-02 15:24:48 +02:00
Benjamin Kampmann 67e63c0d35 ci: update xtask, add ci to build examples 2022-08-02 15:13:27 +02:00
Benjamin Kampmann 4c7ddd7512 refactor: move examples from crates/matrix-sdk into separate crates in examples/ 2022-08-02 15:06:04 +02:00
Jonas Platte 9fca639f9b feat(sdk): Add room::Common::add_event_handler 2022-08-02 13:38:30 +02:00
Benjamin Kampmann 6cb87c64b5 Merge pull request #855 from gnunicorn/gnunicorn/issue833
Integration tests against an actual synapse server
2022-08-02 12:22:48 +02:00
Benjamin Kampmann a5875ff75d ci: update codecov config to exclude all testing crates 2022-08-02 10:59:18 +02:00
Benjamin Kampmann 9bb02f2419 fix: fix path to testing crate 2022-08-02 10:58:53 +02:00
Benjamin Kampmann 65be06ebad Merge remote-tracking branch 'origin/main' into gnunicorn/issue833 2022-08-02 10:32:09 +02:00
Damir Jelić 154538c4c9 fix(crypto): Make the secret receiving logic more obvious.
This patch ensures that the received secret has been sent by one of our
verified devices. We already ensure so for the secrets we import, since
we check that the cross signing keys match the public keys of our own
user idenity.

No other secrets get imported by this method but it could quite easily
become an issue if we start accepting more secret types.
2022-08-02 09:35:38 +02:00
Damir Jelić f23c16cf88 refactor(crypto): Split out the methods to receive a secret 2022-08-02 09:35:38 +02:00
Jonas Platte 165973121c fix(sdk): Make remove_event_handler work with room-specific handlers
As part of that, store only the ID, not the whole key in
EventHandlerWrapper, because the key is getting bigger with room_id.
2022-08-01 23:57:00 +02:00
Jonas Platte 31903d3cbc chore(sdk): Simplify remove_event_handler test 2022-08-01 23:55:44 +02:00
Jonas Platte 108f299e92 refactor(sdk): Inline handle_sync_events_wrapped_with into all callers
It was too generic to be readable.
2022-08-01 23:55:44 +02:00
Jonas Platte eb78815be5 refactor(sdk): Create non-generic fn call_event_handlers
Extracted out of handle_sync_events_wrapped_with.
2022-08-01 23:55:44 +02:00
Jonas Platte 0716f6afaa feat(sdk): Add Client::add_room_event_handler 2022-08-01 23:55:44 +02:00
Jonas Platte 78169d516e refactor(sdk): Avoid unnecessary double map lookup 2022-08-01 23:55:44 +02:00
Jonas Platte 061fe104a9 chore(sdk): Move EventHandler{Fut,Fn} into event_handler module 2022-08-01 23:55:43 +02:00
Benjamin Kampmann f91bdb28ba Merge pull request #897 from gnunicorn/ben-fix-documentation-generation
Fix documentation generation
2022-08-01 22:37:21 +02:00
Benjamin Kampmann 14429af511 Update .github/workflows/documentation.yml
Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2022-08-01 22:19:46 +02:00
Benjamin Kampmann 1e6e00b0c0 ci(integration-testing): split integration test off from regular tests 2022-08-01 18:54:33 +02:00
docweirdo 744bd8d0d2 feat(common): Add a generically usable timeout function 2022-08-01 18:23:30 +02:00
Benjamin Kampmann 06ad079099 Merge pull request #895 from matrix-org/ben-add-editoconfig
build: Adding .editorconfig
2022-08-01 15:23:58 +02:00
Benjamin Kampmann 7dfadd1848 ci(integration-testing): split integration test off from regular tests 2022-08-01 15:20:13 +02:00
Benjamin Kampmann 5089c1a7e9 ci(docs): force npm generated bindings if existing 2022-08-01 14:56:26 +02:00
Benjamin Kampmann c15a4bcdd4 build: Adding .editorconfig 2022-08-01 14:33:05 +02:00
Benjamin Kampmann 729836cf70 Merge pull request #894 from gnunicorn/ben-add-js-docs
ci(js) add npm docs of bindings to pages
2022-08-01 13:35:17 +02:00
Benjamin Kampmann 2d21c30639 Merge remote-tracking branch 'origin/main' into gnunicorn/issue833 2022-08-01 13:28:28 +02:00
Benjamin Kampmann 3842426788 ci: Remove unneeded github action params 2022-08-01 13:27:00 +02:00
Benjamin Kampmann c27016b2e7 testing: fix review grumbles 2022-08-01 13:01:50 +02:00
Benjamin Kampmann d0f58d0879 ci(js) add npm docs of bindings to pages 2022-08-01 12:17:36 +02:00
Kévin Commaille 4cdc91844e chore(ci): Fix new clippy warnings 2022-07-30 12:58:18 +02:00
Jonas Platte fe5d8fb40e refactor: Deprecate ClientBuilder::{crypto_store, state_store} 2022-07-29 17:22:12 +02:00
Jonas Platte ffb4c6d6ef docs: Use new ClientBuilder methods in examples 2022-07-29 17:22:12 +02:00
Jonas Platte 93c4c5d128 feat(sdk): Add ClientBuilder::indexeddb_store 2022-07-29 17:22:12 +02:00
Jonas Platte ec3a6e4b15 feat(sdk): Add ClientBuilder::sled_store 2022-07-29 17:22:12 +02:00
Jonas Platte ecbcf734b9 docs(sled): Clean up make_store_config description 2022-07-29 17:22:12 +02:00
Jonas Platte 75f08aed69 docs(sdk): Fix outdated description of ClientBuilder::store_config 2022-07-29 17:22:12 +02:00
Jonas Platte 255955555d refactor(sdk): Add back register_event_handler[_context] as deprecated methods
… using their original signatures so upgrading matrix-sdk is easier.
2022-07-29 14:39:28 +02:00
Jonas Platte 27b858308c refactor(sdk)!: Rename {register => add}_event_handler[_context]
It's shorter and better fits with the new remove_event_handler method.
2022-07-29 14:39:28 +02:00
Jonas Platte a21ac5d6e4 Disable debug-info for the dev profile 2022-07-29 11:28:47 +02:00
Damir Jelić 4db041ce9a docs(crypto): Improve the wording on the recipient collection method
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-07-29 10:20:36 +02:00
Damir Jelić c194e08942 fix(crypto): Rotate the outbound group session if the algorithm changes 2022-07-29 10:20:36 +02:00
Damir Jelić 713e96d3a8 fix(crypto): Store intermediate changes to the outbound group session
This fixes a bug where we would lose already generated to-device
requests carrying an m.room_key when the client gets restarted.

This only happens if the request was generated after the initial
share of the room key. Such requests get generated if a new device
or member join the group.
2022-07-29 10:01:58 +02:00
Jonas Platte b36f79a1bb doc: Make rustfmt format code blocks in documentation 2022-07-28 20:06:12 +02:00
Jonas Platte 504464c1e8 chore: Remove edition from .rustfmt.toml
It will be picked up from Cargo.toml.
2022-07-28 20:06:12 +02:00
Doug b884c5baae chore(bindings/ffi): Print each step when building XCFramework. 2022-07-28 13:37:02 +02:00
Doug c15fb5e5b7 fix(sdk): Reduce retry length on homeserver discovery 2022-07-28 13:37:02 +02:00
Damir Jelić 0555216f37 chore(crypto): Introduce a helper function to convert events in tests 2022-07-28 12:48:12 +02:00
Damir Jelić 03a4814a1a refactor(crypto): Use our own type for megolm 2022-07-28 12:48:12 +02:00
Damir Jelić 80c7f057cc refactor(crypto): Use our own struct for encrypted to-device events 2022-07-28 12:48:12 +02:00
Damir Jelić 7e70997e1c feat(crypto): Add customized m.room.encrypted events 2022-07-28 12:48:12 +02:00
Damir Jelić 2bb7914611 refactor(crypto): Make the event type an associated constant 2022-07-28 12:48:12 +02:00
Benjamin Kampmann c8c793da98 Merge pull request #768 from gnunicorn/gnunicorn/issue756
Bump db version, "upgrade" path to new db in sled and indexeddb
2022-07-28 09:51:27 +02:00
Damir Jelić e501979d92 feat(bindings/ffi): Allow setting the timeline limit for the sync setup 2022-07-27 19:23:10 +02:00
Doug 57c1e68893 Rename limit to timeline_limit.
Use a u16 instead.
2022-07-27 18:03:04 +01:00
Doug a875c4b373 Expose room membership in the FFI. 2022-07-27 17:23:09 +01:00
Doug f6e215dac3 Add limit parameter to start_sync. 2022-07-27 17:22:17 +01:00
Jonas Platte 4415ed9b58 refactor: Improve tracing events
* Use fields more
* Adjust some wording
2022-07-27 17:47:01 +02:00
Benjamin Kampmann 59fc81b957 ci(xtask): bump wasm timeout to make the ci pass 2022-07-27 16:58:20 +02:00
Benjamin Kampmann 770ac193a7 fix(indexeddb): import errors 2022-07-27 15:08:51 +02:00
Benjamin Kampmann fe40b3753a Merge pull request #870 from Hywan/feat-crypto-js-npm-publish
chore: Update or add `git-cliff` config' & prepare `crypto-js` for publishing
2022-07-27 14:28:40 +02:00
Benjamin Kampmann e871114436 docs(crypto-ffi): fixing path 2022-07-27 13:43:39 +02:00
Benjamin Kampmann fb4dd4d875 test(crypto-js): fixing path 2022-07-27 13:32:26 +02:00
Benjamin Kampmann 5806b3fbe9 ci(crypto-js): use a specific node version to run tests 2022-07-27 13:23:44 +02:00
Benjamin Kampmann e1be222558 style: fixing fmt 2022-07-27 13:09:08 +02:00
Benjamin Kampmann 37f0926832 chore: prefer to user Store::builder 2022-07-27 12:59:53 +02:00
Benjamin Kampmann 936731065b fix(indexeddb): specify futures import 2022-07-27 12:53:54 +02:00
Benjamin Kampmann 088699b6a4 docs(xtask): remove comment 2022-07-27 12:20:46 +02:00
Benjamin Kampmann b2da8b7dd7 fix(examples): update to renaming 2022-07-27 12:12:17 +02:00
Benjamin Kampmann b80d3c2f2d Merge remote-tracking branch 'origin/main' into gnunicorn/issue756 2022-07-27 12:04:56 +02:00
Benjamin Kampmann eb1dfd9690 docs(indexeddb): document builder functions 2022-07-27 11:52:13 +02:00
Benjamin Kampmann 54c181a9ad chore: cleaning up builder usage in sled and indexeddb 2022-07-27 11:51:56 +02:00
Benjamin Kampmann 003c946581 chore(sled): ascending order for dependencies 2022-07-27 10:59:02 +02:00
docweirdo 82731a5e89 feat(sdk): Add support for removing event handlers 2022-07-27 05:09:22 +00:00
Damir Jelić 4175e6badf feat(bindings/ffi): Allow clients to be restored using only an access token
This functionality is important to allow clients to log in using an OIDC server. The OIDC server returns only an access token, the client needs to fetch the user ID and device ID from the Matrix server separately.

This lives in the bindings for now since OIDC support in Matrix is being actively developed.
2022-07-26 17:10:50 +02:00
Benjamin Kampmann 008c0f4659 Merge pull request #860 from matrix-org/doug/client-path
Use a consistent store path when logging in through the FFI.
2022-07-26 16:33:32 +02:00
Doug a951a273e7 Rebase and rename method.
Add a device ID parameter to restore_with_access_token
2022-07-26 15:00:02 +01:00
Doug f13f590b7f Add login_access_token to the FFI auth service. 2022-07-26 15:00:02 +01:00
Doug 1eadd0ba2d Revert changes to store path. Use whoami for path. 2022-07-26 14:50:51 +01:00
Doug 3b8a2ca2d7 Add missing path method to IndexeddbStore. 2022-07-26 14:50:51 +01:00
Doug b6b1f904e0 Fix clippy warning. 2022-07-26 14:50:51 +01:00
Doug f31528c926 Allow a client to be built without a store path.
Throw an error on login if it is missing.
2022-07-26 14:50:51 +01:00
Doug 05fab8c394 Add a store_path method on the FFI client. 2022-07-26 14:48:37 +01:00
Jonas Platte e37e62c92c chore(sdk): Fix event handler test
It wasn't actually testing typing and power-levels event handlers before.
2022-07-26 11:11:10 +02:00
Jonas Platte d162dd07d5 refactor(sdk): Split SyncEvent::ID into KIND and TYPE
This should make things slightly easier to read.
2022-07-25 20:33:43 +02:00
Jonas Platte 42d3ebbe87 chore: Remove ugly commas right before closing parentheses 2022-07-25 20:30:13 +02:00
Jonas Platte 4e7bd6760a chore: Replace qualified uses of tracing macros with imports 2022-07-25 20:30:13 +02:00
Jonas Platte ab9476960b chore(sled): Fix line length overflow 2022-07-25 20:30:13 +02:00
Jonas Platte b3092bd1d9 chore(indexeddb): Remove unnecessary .to_string() call in test code 2022-07-25 20:30:13 +02:00
Jonas Platte c2ad4b7d82 fix(appservice): Respond with a non-stringified empty object
warp::reply::json() serializes its argument to JSON, passing it an
already-serialized JSON string doesn't do the right thing.
2022-07-25 20:30:13 +02:00
Jonas Platte f8e729f7f3 chore: Use implicit named arguments for formatting macros more 2022-07-25 20:30:12 +02:00
Jonas Platte 5c88bd1595 refactor(sdk): Remove ID from EventHandler
Turns out it wasn't actually needed.
2022-07-25 20:30:12 +02:00
Damir Jelić 204441f2b3 Fix a formatting issue in a doc example
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-07-25 19:06:15 +02:00
Damir Jelić 79d13148fb chore: Remove TryFrom/TryInto imports 2022-07-25 19:06:15 +02:00
Julian Sparber c247de95b6 client: Remove redudant delay after sync error
If the last sync was less then a 1s ago we wait for a 1s, it doesn't
make sense to wait an additional second on an error. Also the stream
sync api returns the error after a delay of 1s, which doesn't make
sense.
2022-07-25 12:01:28 +02:00
Johannes Becker 6d87dd8011 refactor(appservice)!: Make USER_MEMBER const private" 2022-07-22 09:33:42 +02:00
Jonas Platte 4b6a28da0d chore(sdk): Fix line length overflow in common.rs 2022-07-21 19:24:42 +02:00
Benjamin Kampmann bf6e1b594d fix(integration-tests): requires multi-threading feature from tokio 2022-07-21 18:06:48 +02:00
Benjamin Kampmann 59332e46b5 switch to multithreaded tokio test runner 2022-07-21 17:58:50 +02:00
Benjamin Kampmann c070b96a68 Merge remote-tracking branch 'origin/main' into gnunicorn/issue833 2022-07-21 17:37:49 +02:00
Benjamin Kampmann d68d6ead69 chore: let's check the logs 2022-07-21 17:31:58 +02:00
Benjamin Kampmann f34c2f0574 ci: fix typos 2022-07-21 17:28:38 +02:00
Benjamin Kampmann aad167b792 style(indexeddb): fixing rust fmt 2022-07-21 17:25:43 +02:00
Benjamin Kampmann 814a30064f fix(sled-inspector): new builder fn 2022-07-21 17:18:43 +02:00
Ivan Enderlin 6ce5d0b507 chore(crypto-js): Add cliff.toml for git-cliff. 2022-07-21 15:50:11 +02:00
Ivan Enderlin db7824efbd chore(crypto-nodejs): Update Conventional Commits types. 2022-07-21 15:49:26 +02:00
Ivan Enderlin ee4702d04a feat(bindings/crypto-js) Update package name, and use package scope. 2022-07-21 15:47:55 +02:00
Ivan Enderlin d68b6ea64d doc: Fix formatting. 2022-07-21 15:47:00 +02:00
Ivan Enderlin 49bc950fdd doc: Add missing scope for matrix-sdk-crypto. 2022-07-21 15:46:21 +02:00
Ivan Enderlin 7001113877 doc: Propose conventional commits scopes & types
doc: Propose conventional commits scopes & types
2022-07-21 15:44:43 +02:00
Ivan Enderlin c0d3099e2e doc: Propose conventional commits types. 2022-07-21 15:43:31 +02:00
Ivan Enderlin 49e5d73d83 doc: Propose conventional commits scopes. 2022-07-21 13:59:31 +02:00
Benjamin Kampmann 2ffaaeeae2 docs(sled): fixing examples and utils 2022-07-21 13:18:15 +02:00
Benjamin Kampmann 60eef8967f fix(sled-store): apply review requests 2022-07-21 11:52:04 +02:00
Benjamin Kampmann ed893ed5b0 Merge remote-tracking branch 'origin/main' into gnunicorn/issue756 2022-07-21 11:38:10 +02:00
Jonas Platte 4e2bd14a27 chore: Silence buggy clippy lint 2022-07-21 11:30:13 +02:00
Jonas Platte 0b3b757120 refactor: Make Store type internal to base crate 2022-07-21 11:30:13 +02:00
Jonas Platte 63d01dd20e refactor(base): Stop using Store type in store integration tests 2022-07-21 11:30:13 +02:00
Jonas Platte a43005dec2 refactor(base): Add RoomInfo::new constructor 2022-07-21 11:30:13 +02:00
Jonas Platte 4187aa400f refactor: Remove Store type usage from sled-state-inspector
Requires using more explicit syntax for some calls because SledStore has
methods of the same names with different (harder to use) signatures.
2022-07-21 11:30:13 +02:00
Jonas Platte cf8f3bf7cc refactor(sdk)!: Update MessageOptions API once again
… to match the latest changes in Ruma.
2022-07-21 11:18:09 +02:00
Jonas Platte 5b66bed1f0 chore: Remove unused imports from example 2022-07-21 11:18:09 +02:00
Benjamin Kampmann 1668c173e6 Merge pull request #836 from matrix-org/ben-release-crypto-nodes-beta0
Release crypto-nodejs beta1
2022-07-21 10:59:46 +02:00
Jonas Platte 76fd9bd963 fix(sdk): Call to-device handlers 2022-07-21 10:30:13 +02:00
Benjamin Kampmann 6f18e35b72 Merge remote-tracking branch 'origin/main' into ben-release-crypto-nodes-beta0 2022-07-21 10:25:26 +02:00
Johannes Becker 5f7e91c2e7 refactor(appservice)!: Cleanup 2022-07-21 09:01:53 +02:00
Jonas Platte 45829efa7e feat(bindings): Tracing configuration through FFI 2022-07-20 16:38:51 +02:00
Kévin Commaille a6bd7fc82f chore: Update Ruma 2022-07-20 12:18:06 +00:00
Stefan Ceriu 5c53a5f699 chore(bindings/apple): Remove unnecessary +nightly flag from debug builds 2022-07-20 14:53:00 +03:00
Stefan Ceriu 021bf55074 feat(bindings/sdk-ffi): Expose full tracing configuration string through ffi. 2022-07-20 13:39:15 +03:00
Stefan Ceriu 0043de4028 Update bindings/matrix-sdk-ffi/Cargo.toml
Co-authored-by: Jonas Platte <jplatte@matrix.org>
2022-07-20 13:28:58 +03:00
Stefan Ceriu f9bb86c52f feat(bindings/sdk-ffi): Allow ffi users to configure tracing and log levels 2022-07-20 12:56:42 +03:00
Stefan Ceriu 1cd18f49aa chore(bindings/apple): Remove now unnecessary debug script module import following module map rename 2022-07-20 12:29:36 +03:00
Benjamin Kampmann 8b160dfd38 Merge pull request #857 from matrix-org/doug/homeserver-details
Add a `HomeserverLoginDetails` to the FFI's auth service.
2022-07-20 10:27:09 +02:00
Doug 77f7dbbbc8 Support server URLs. Join homeserver details futures. 2022-07-19 16:16:03 +01:00
Ivan Enderlin fe590735c1 fix(bindings/crypto-nodejs): Fix CI typo
fix(bindings/crypto-nodejs): Rrrrr
2022-07-19 16:43:40 +02:00
Ivan Enderlin 0360b13cdb fix(bindings/crypto-nodejs): Rrrrr 2022-07-19 16:12:43 +02:00
Jonas Platte f30419446f refactor(sdk)!: Make from in MessageOptions optional 2022-07-19 15:25:16 +02:00
Doug 93d879f356 Add homeserver_details property. 2022-07-19 12:48:00 +01:00
Benjamin Kampmann c20c23bc19 Merge pull request #856 from matrix-org/fixing-clippy-lint
style(crypto): use matches for legibility
2022-07-19 12:54:44 +02:00
Johannes Becker a174d0f669 sdk: Make from in MessageOptions optional 2022-07-19 12:46:48 +02:00
Doug a10a26a68d Tidy up authentication service.
Add HomeserverLoginDetails
2022-07-19 11:36:34 +01:00
Benjamin Kampmann 1940ebefcb style(crypto): use matches for legibility 2022-07-19 12:22:53 +02:00
Benjamin Kampmann b91217f53b Merge remote-tracking branch 'origin/main' into gnunicorn/issue833 2022-07-19 12:19:20 +02:00
Benjamin Kampmann 3d3db96734 style(integration-tests): minor rust fmt fixes 2022-07-19 12:11:13 +02:00
Benjamin Kampmann b24f7b7ad7 ci(integration-tests): switch to non-docker github action 2022-07-19 12:05:49 +02:00
Benjamin Kampmann 9502d32941 Merge remote-tracking branch 'origin/main' into gnunicorn/issue833 2022-07-19 12:03:47 +02:00
Benjamin Kampmann 9318c2f1e8 ci(crypto-nodejs): switch to main branch 2022-07-19 11:36:30 +02:00
Benjamin Kampmann 2d03cb5c79 Merge pull request #852 from zecakeh/test-bulk-member
test(sdk): Split tests for permalinks
2022-07-19 11:34:48 +02:00
Johannes Becker aa8206d6c8 chore: Bump ruma 2022-07-18 19:32:16 +02:00
Johannes Becker f937d82336 chore: Bump ruma 2022-07-18 16:46:34 +00:00
Benjamin Kampmann 3f71818704 ci(test): switch from docker to compose 2022-07-18 18:10:41 +02:00
Benjamin Kampmann 025db83af3 ci(test): cancel previous calls and fix the docker logs cmd 2022-07-18 18:06:54 +02:00
Benjamin Kampmann 8520976417 style(test): fix styles of integration tests 2022-07-18 17:55:31 +02:00
Benjamin Kampmann 06cab75df3 ci(sdk): Add integration tests to CI 2022-07-18 17:39:51 +02:00
Benjamin Kampmann 47a8c62f44 fix(sdk): fetch invitiation details without sync 2022-07-18 17:39:29 +02:00
Benjamin Kampmann e2f1f01cb2 test(sdk): creating integration tests for invitations 2022-07-18 17:33:09 +02:00
Ivan Enderlin fdef2dd86d feat(bindings/crypto-js): Redirect panics and logs into JavaScript console
feat(bindings/crypto-js): Redirect panics and logs into JavaScript console
2022-07-18 14:59:50 +02:00
Ivan Enderlin c72ec36b3a chore(style) Make cargo fmt happy. 2022-07-18 14:39:39 +02:00
Benjamin Kampmann e3febd6f1f refactore(test): move testing out of regular build environment 2022-07-18 14:37:51 +02:00
Kévin Commaille b98e3d80a0 test(sdk): Split tests for permalinks 2022-07-18 13:39:37 +02:00
Kévin Commaille c0a7b17324 test: Add method to create room member events in bulk 2022-07-18 13:39:37 +02:00
Kévin Commaille 66c5d5311e test: Add methods to add events in bulk 2022-07-18 13:39:36 +02:00
Stefan Ceriu f215c92d0b fix(bindings/apple): Remove briding header as no longer needed after corectly naming the module map 2022-07-18 13:09:39 +02:00
Stefan Ceriu d571ca718d fix(bindings/apple): Allow any platform OS simulator to run the Apple specific unit tests. 2022-07-18 13:09:39 +02:00
Kévin Commaille 37eb058dac test: Reorganize JSON responses and events 2022-07-18 10:46:45 +02:00
Kévin Commaille 45ecd89387 test: Build sync response per room and event type
Allow to have more customizable and complete responses
2022-07-18 10:46:45 +02:00
Kévin Commaille 5d916e4a67 test: Expose default room ID used in sync JSON responses 2022-07-18 10:46:45 +02:00
Ivan Enderlin daa0fc0206 doc(bindings/crypto-js): Fix typos. 2022-07-18 10:05:05 +02:00
Ivan Enderlin 3d1c96fbec feat(bindings/crypto-js): Redirect errors to console.error. 2022-07-18 09:51:44 +02:00
Ivan Enderlin decd3fcb43 feat(bindings/crypto-js) Simplify code for feature = "tracing".
This patch creates one `inner` module for when `feature = "tracing"`,
and one for when `no(feature = "tracing")`. Then, let's expose
everything from `inner::*`.

This patch also replaces `Tracing.install` by `new Tracing`. In case
of `not(feature = "tracing")`, `new Tracing` raises an error.

The goal is to remove all the `#[cfg(…)]` annotations everywhere. Now
there is only 2 of them.
2022-07-18 09:44:42 +02:00
Ivan Enderlin c763ce3f41 feat(bindings/crypto-js): Tracing can be installed more than once. 2022-07-18 09:23:37 +02:00
Johannes Becker 09c56ea057 feat(appservice)!: Allow specifying device id for registration 2022-07-14 15:23:21 +02:00
github-actions 163ce94806 Tagging Crypto-Node.js for release 2022-07-14 10:40:57 +00:00
Benjamin Kampmann ebfa235dab chore(crypto-nodejs): Update changelog for beta.1 2022-07-14 12:31:04 +02:00
Benjamin Kampmann 0112396c99 ci(crypto-nodejs): trigger build for new tag 2022-07-14 12:31:04 +02:00
Benjamin Kampmann 8170d2c996 Merge remote-tracking branch 'origin/main' into ben-release-crypto-nodes-beta-1 2022-07-14 11:40:45 +02:00
Benjamin Kampmann 41de3e0af8 Merge pull request #844 from Hywan/fix-issue-842
fix(bindings/crypto-nodejs): Fix pre-built download link
2022-07-14 11:36:15 +02:00
Benjamin Kampmann 81bf300000 Merge pull request #839 from johannescpk/appservice/virtual-users
feat(appservice): Add method to get virtual user map
2022-07-14 11:31:02 +02:00
Benjamin Kampmann 594e8c04cd Merge pull request #840 from matrix-org/jplatte/optional-dep-features
Remove implicit features for optional dependencies
2022-07-14 11:30:03 +02:00
Benjamin Kampmann b6d94ab7c6 Merge pull request #845 from Hywan/fix-issue-843
fix(bindings/crypto-js): Use `cross-env` to pass envvar on Windows
2022-07-14 11:28:57 +02:00
Ivan Enderlin 283c5ff51e fix(bindings/crypto-js): Let's not deal with Console.group.
Events and spans from `tracing` can happen asynchronously, and could
mess the `Console.group` structure.
2022-07-14 09:04:44 +02:00
Ivan Enderlin bb631f2f79 feat(bindings/crypto-js): Add ability to turn Tracing on and off, and change logger min level.
The patch updates the code to use `tracing_subscriber::reload`, so
that we get a `Handle` that can be used to modify the tracing at
runtime.

This patch also adds a new `tracing` feature.

This patch finally adds a test suite for the `Tracing` API.
2022-07-14 09:04:13 +02:00
Ivan Enderlin d39baf1295 test(bindings/crypto-js): Encrypt and decrypt a valid message. 2022-07-14 09:04:13 +02:00
Ivan Enderlin f5016dbb97 feat(bindings/crypto-js): Define Layer.max_level_hint. 2022-07-14 09:04:13 +02:00
Ivan Enderlin e7d0ee4379 !fixup 2022-07-14 09:04:13 +02:00
Ivan Enderlin 70561f9649 feat(bindings/crypto-js): Add the userLogger function to enable logging into Console. 2022-07-14 09:04:13 +02:00
Ivan Enderlin 8f8bd40e8d feat(bindings/crypto-js): Redirect Rust panics to JavaScript console. 2022-07-14 09:04:13 +02:00
Ivan Enderlin 04d326eec1 doc(bindings/crypto-nodejs): Fix package name in the README.md
Fix package name in readme for nodejs bindings
2022-07-14 08:39:53 +02:00
Ivan Enderlin 3567d19359 fix(bindings/crypto-js): Use cross-env to pass envvar on Windows. 2022-07-14 08:26:57 +02:00
Ivan Enderlin 65b1dfef6f fix(bindings/crypto-nodejs): Fix pre-built download link. 2022-07-14 08:14:45 +02:00
Travis Ralston 4f1718e587 Fix package name in readme for nodejs bindings 2022-07-13 13:45:07 -06:00
Jonas Platte 3c5f30d41e refactor: Remove implicit features for optional dependencies
Consistently use `dep:` syntax for optional dependencies so they don't
implicitly act as features of their own.
2022-07-13 18:55:41 +02:00
Jonas Platte 529bdc8e0a refactor: Remove unused optional dependencies 2022-07-13 18:23:52 +02:00
Johannes Becker f55a86dd66 feat(appservice): Add method to get virtual user map 2022-07-13 17:16:12 +02:00
Benjamin Kampmann f87764fabb ci(ffi-apple): fixing ffi build for apple
Merge pull request #806 from matrix-org/stefan/ffi-workflow-optimization
2022-07-13 11:58:26 +02:00
Stefan Ceriu 65654de7eb chore: sdk-ffi apple - try building the framework on x86_64 outside of Xcode 2022-07-13 11:19:07 +02:00
Stefan Ceriu 399bbc25e9 chore: sdk-ffi apple - run the CI build script from the Xcode project and only for the active architecture 2022-07-13 11:19:06 +02:00
Stefan Ceriu c61dbb657e chore: sdk-ffi apple - drop sample project deployment target to iOS 15 and macOS 12, disable catalyst. 2022-07-13 11:19:06 +02:00
Stefan Ceriu a73b104c59 chore: sdk-ffi apple - rename modulemap to module.modulemap as per xcframework specifications 2022-07-13 11:19:06 +02:00
Stefan Ceriu c10961f068 chore: sdk-ffi apple - remove mac catalyst target support 2022-07-13 11:19:06 +02:00
Benjamin Kampmann 15e22cba47 Merge pull request #837 from johannescpk/appservice/refactor-cleanup
refactor(appservice)!: Improve API and cleanup docs
2022-07-13 11:16:47 +02:00
Johannes Becker ec00af0bca refactor(appservice)!: Improve API and cleanup docs 2022-07-13 10:11:43 +02:00
Benjamin Kampmann a4f1c404f6 ci(crypto-nodejs): set npm publish level to public
Release Crypto-Node.js / Upload prebuilt libraries (gcc-aarch64-linux-gnu g++-aarch64-linux-gnu, ubuntu-latest, aarch64-unknown-linux-gnu) (push) Failing after 45s
Release Crypto-Node.js / Upload prebuilt libraries (gcc-arm-linux-gnueabihf, ubuntu-latest, arm-unknown-linux-gnueabihf) (push) Failing after 41s
Release Crypto-Node.js / Upload prebuilt libraries (gcc-i686-linux-gnu g++-i686-linux-gnu, ubuntu-latest, i686-unknown-linux-gnu) (push) Failing after 30s
Release Crypto-Node.js / Upload prebuilt libraries (ubuntu-latest, x86_64-unknown-linux-gnu) (push) Failing after 42s
Release Crypto-Node.js / Upload prebuilt libraries (ubuntu-latest, x86_64-unknown-linux-musl) (push) Failing after 35s
Release Crypto-Node.js / Upload prebuilt libraries (macos-latest, aarch64-apple-darwin) (push) Has been cancelled
Release Crypto-Node.js / Upload prebuilt libraries (macos-latest, x86_64-apple-darwin) (push) Has been cancelled
Release Crypto-Node.js / Upload prebuilt libraries (windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
Release Crypto-Node.js / Upload prebuilt libraries (windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
Release Crypto-Node.js / Upload prebuilt libraries (windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
Release Crypto-Node.js / Package nodejs package (push) Has been cancelled
2022-07-12 19:28:59 +02:00
Benjamin Kampmann f69123b0d8 style(indexeddb): rust fmt 2022-07-12 13:11:50 +02:00
Benjamin Kampmann c1f0c73728 fix(indexeddb): New migration didn't create schema for new db 2022-07-11 16:18:29 +02:00
Benjamin Kampmann 54ed1af223 Merge remote-tracking branch 'origin/main' into gnunicorn/issue756 2022-07-11 15:17:03 +02:00
Benjamin Kampmann 6a57461f74 feat(indexeddb)!: Implement StateStoreBuilder pattern to configure migration preferences 2022-07-11 14:28:09 +02:00
Benjamin Kampmann d7b974ac04 build(xtask): indexeddb alias for ci wasm-commands 2022-07-07 18:35:54 +02:00
Benjamin Kampmann aae9b5c6d8 refactor(test)!: More debugging info on errors in async_test macro 2022-07-07 18:35:16 +02:00
Benjamin Kampmann dc40309cbe feat(sled): Introduce SledStoreBuilder, allow migration conflict strategy configuration 2022-07-06 17:51:51 +02:00
Benjamin Kampmann d465b70bea refactor(indexeddb)!: Rename SerializationError to IndexedDBStoreError 2022-07-06 12:18:58 +02:00
Benjamin Kampmann 5316d2e6e7 fix(indexeddb): Upgrade version to latest state store pattern 2022-06-16 13:17:45 +02:00
Benjamin Kampmann 7adef1d24e fix(sled): Upgrade db version to latest changes 2022-06-16 13:17:35 +02:00
329 changed files with 31035 additions and 9925 deletions
+5
View File
@@ -0,0 +1,5 @@
[profile.default]
retries = 2
# kill the slow tests if they still aren't up after 180s
slow-timeout = { period = "60s", terminate-after = 3 }
+6
View File
@@ -0,0 +1,6 @@
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
-54
View File
@@ -1,54 +0,0 @@
name: AppService
on:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
CARGO_TERM_COLOR: always
jobs:
test-appservice:
if: github.event_name == 'push' || !github.event.pull_request.draft
name: ${{ matrix.os-name }} [m]-appservice
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
matrix:
include:
- os: ubuntu-latest
os-name: 🐧
- os: macos-latest
os-name: 🍏
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Run checks
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-appservice
+1 -1
View File
@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
+11 -7
View File
@@ -26,7 +26,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest]
node-version: [14.0, 16.0, 18.0]
include:
- os: ubuntu-latest
@@ -34,13 +34,14 @@ jobs:
- os: macos-latest
os-name: 🍏
node-version: 18.0
- node-version: 18.0
build-doc: true
steps:
- name: Checkout the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
@@ -98,7 +99,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
@@ -113,6 +114,8 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.0
- name: Install NPM dependencies
working-directory: ${{ env.MATRIX_SDK_CRYPTO_JS_PATH }}
@@ -159,15 +162,16 @@ jobs:
with:
command: install
# keep in sync with uniffi dependency in Cargo.toml's
args: uniffi_bindgen --version ^0.18
args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev 091c3561656e72e1a4160412c83b36d98e556d06
- name: Generate .xcframework
run: sh bindings/apple/debug_build_xcframework.sh ci
working-directory: bindings/apple
run: sh ./debug_build_xcframework.sh x86_64 ci
- name: Run XCTests
working-directory: bindings/apple
run: |
xcodebuild test \
-project bindings/apple/MatrixRustSDK.xcodeproj \
-scheme MatrixRustSDK \
-sdk iphonesimulator \
-destination 'platform=iOS Simulator,name=iPhone 13,OS=15.4'
-destination 'platform=iOS Simulator,name=iPhone 13'
+266 -21
View File
@@ -16,8 +16,46 @@ env:
CARGO_TERM_COLOR: always
jobs:
cancel-others:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.1
with:
access_token: ${{ github.token }}
xtask:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Check xtask cache
uses: actions/cache@v3
id: xtask-cache
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Install rust stable toolchain
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Build
if: steps.xtask-cache.outputs.cache-hit != 'true'
uses: actions-rs/cargo@v1
with:
command: build
args: -p xtask
test-matrix-sdk-features:
name: 🐧 [m], ${{ matrix.name }}
needs: xtask
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
@@ -51,39 +89,87 @@ jobs:
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-features ${{ matrix.name }}
test-matrix-sdk-crypto:
name: 🐧 [m]-crypto
test-matrix-sdk-examples:
name: 🐧 [m]-examples
needs: xtask
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-crypto
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci examples
test-matrix-sdk-crypto:
name: 🐧 [m]-crypto
needs: xtask
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Test
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-crypto
test-all-crates:
name: ${{ matrix.name }}
@@ -127,7 +213,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: nextest
args: run --workspace
args: run --workspace --exclude matrix-sdk-integration-testing
- name: Test documentation
uses: actions-rs/cargo@v1
@@ -137,6 +223,7 @@ jobs:
test-wasm:
name: 🕸️ ${{ matrix.name }}
needs: xtask
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
@@ -174,7 +261,7 @@ jobs:
steps:
- name: Checkout the repo
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
@@ -196,6 +283,12 @@ jobs:
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Rust Check
uses: actions-rs/cargo@v1
with:
@@ -207,3 +300,155 @@ jobs:
with:
command: run
args: -p 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: 🐧
- os: macos-latest
os-name: 🍏
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install nextest
uses: taiki-e/install-action@nextest
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Run checks
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci test-appservice
formatting:
name: Check Formatting
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: rustfmt
profile: minimal
override: true
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
typos:
name: Spell Check with Typos
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v3
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@master
clippy:
name: Run clippy
needs: xtask
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Get xtask
uses: actions/cache@v3
with:
path: target/debug/xtask
key: xtask-${{ hashFiles('xtask/**') }}
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci clippy
integration-tests:
name: Integration test
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: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- 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
uses: actions-rs/cargo@v1
with:
command: nextest
args: run -p matrix-sdk-integration-testing
+15 -1
View File
@@ -17,7 +17,9 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Install Rust
uses: actions-rs/toolchain@v1
@@ -35,6 +37,18 @@ jobs:
command: install
args: cargo-tarpaulin
# set up backend for integration tests
- uses: actions/setup-python@v4
with:
python-version: 3.8
- uses: gnunicorn/setup-matrix-synapse@main
with:
uploadLogs: true
httpPort: 8228
disableRateLimiting: true
serverName: "matrix-sdk.rs"
- name: Run tarpaulin
uses: actions-rs/cargo@v1
with:
+30 -4
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
@@ -22,11 +22,16 @@ jobs:
toolchain: nightly
override: true
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18
- name: Load cache
uses: Swatinem/rust-cache@v1
# Keep in sync with xtask docs
- name: Build documentation
- name: Build rust documentation
uses: actions-rs/cargo@v1
env:
# Work around https://github.com/rust-lang/cargo/issues/10744
@@ -34,12 +39,33 @@ jobs:
RUSTDOCFLAGS: "--enable-index-page -Zunstable-options --cfg docsrs -Dwarnings"
with:
command: doc
args: --no-deps --workspace --features docsrs
args: --no-deps --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
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./target/doc/
publish_dir: ./doc/
force_orphan: true
@@ -38,6 +38,8 @@ 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
@@ -98,12 +100,18 @@ jobs:
tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }}
body_path: "${{ env.PKG_PATH }}/CHANGELOG.md"
# finally, let's create a PR for all this, too
# let's create a PR for all this, too
- name: Create Pull Request
uses: peter-evans/create-pull-request@v4
with:
branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}"
base: "main"
title: "Preparing Release ${{ env.TAG_PREFIX }}${{ inputs.version }}"
body: |
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }} back into main
Automatic Pull-Request to merge release ${{ env.TAG_PREFIX }}${{ inputs.version }}
trigger-release:
# and trigger the tagging release workflow
uses: matrix-org/matrix-rust-sdk/.github/workflows/release-crypto-nodejs.yml@main
needs: ['prepare-release']
name: "Trigger release Workflow"
with:
tag: ${{needs.prepare-release.outputs.tag}}
+23 -1
View File
@@ -21,7 +21,12 @@ on:
push:
tags:
- matrix-sdk-crypto-nodejs-v[0-9]+.*
workflow_call:
inputs:
tag:
description: "The tag to build with"
required: true
type: string
jobs:
upload-assets:
name: "Upload prebuilt libraries"
@@ -57,7 +62,15 @@ jobs:
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: actions-rs/toolchain@v1
with:
@@ -90,7 +103,15 @@ jobs:
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: actions-rs/toolchain@v1
with:
@@ -114,4 +135,5 @@ jobs:
uses: JS-DevTools/npm-publish@v1
with:
package: ${{env.PKG_PATH}}/package.json
access: public
token: ${{ secrets.NPM_TOKEN }}
+63
View File
@@ -0,0 +1,63 @@
# This workflow releases the `matrix-sdk-crypto-js` project.
#
# It is triggered when a new tag appears that matches
# `matrix-sdk-crypto-js-v[0-9]+.*`. This workflow builds the package
# for the binding, run its tests to ensure everything is still
# correct, and publish 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-v[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: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 18.0
- name: Install NPM dependencies
working-directory: ${{ env.PKG_PATH }}
run: npm install
- name: Configure NPM auth token
working-directory: ${{ env.PKG_PATH }}
run: npm set "//registry.npmjs.org/:_authToken" "${{ secrets.NPM_TOKEN }}"
- name: Publish the WebAssembly + JavaScript binding (imply building + testing)
working-directory: ${{ env.PKG_PATH }}
run: npm run publish
- 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
-105
View File
@@ -1,105 +0,0 @@
name: Style
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
types:
- opened
- reopened
- synchronize
- ready_for_review
env:
CARGO_TERM_COLOR: always
jobs:
style:
name: Check Formatting
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: rustfmt
profile: minimal
override: true
- name: Cargo fmt
uses: actions-rs/cargo@v1
with:
command: fmt
args: -- --check
pre-commit-styles:
name: Check Style
if: github.event_name == 'push' || !github.event.pull_request.draft
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Setup Python Env
uses: actions/setup-python@v2
- name: Check Styles with pre-commit
uses: pre-commit/action@v2.0.3
with:
# only run `commit`-hooks, excludes typos, clippy, etc
extra_args: --show-diff-on-failure --hook-stage commit
typos:
name: Spell Check with Typos
needs: [style]
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v2
- name: Check the spelling of the files in our repo
uses: crate-ci/typos@master
clippy:
name: Run clippy
needs: [style]
runs-on: ubuntu-latest
if: github.event_name == 'push' || !github.event.pull_request.draft
steps:
- name: Checkout the repo
uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
components: clippy
profile: minimal
override: true
- name: Load cache
uses: Swatinem/rust-cache@v1
- name: Clippy
uses: actions-rs/cargo@v1
with:
command: run
args: -p xtask -- ci clippy
+1 -1
View File
@@ -1,9 +1,9 @@
Cargo.lock
target
generated
master.zip
emsdk-*
.idea/
.env
## User settings
xcuserdata/
-41
View File
@@ -1,41 +0,0 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.2.0
hooks:
- id: check-yaml
- id: check-toml
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: mixed-line-ending
- repo: local
hooks:
- id: fmt
name: fmt
language: system
types: [file, rust]
entry: cargo fmt -- --check
- id: clippy
name: clippy
stages: [push]
language: system
types: [file, rust]
entry: cargo clippy --all-targets --all
pass_filenames: false
- id: test
name: test
stages: [push]
language: system
files: '\.rs$'
entry: cargo test --lib
pass_filenames: false
- id: typos
name: typos
stages: [push]
language: system
entry: typos
pass_filenames: false
+4 -1
View File
@@ -1,7 +1,10 @@
edition = "2018"
max_width = 100
comment_width = 80
wrap_comments = true
imports_granularity = "Crate"
use_small_heuristics = "Max"
group_imports = "StdExternalCrate"
format_code_in_doc_comments = true
doc_comment_code_block_width = 80
# Workaround for https://github.com/rust-lang/rust.vim/issues/464
edition = "2021"
+6
View File
@@ -4,6 +4,12 @@
Fo = "Fo"
BA = "BA"
UE = "UE"
Ure = "Ure"
Ot = "Ot"
# This is the thead html tag, remove this once typos is updated in the github
# action. 1.3.1 seems to work correctly, while 1.11.0 on the CI seems to get
# this wrong
thead = "thead"
[files]
# Our json files contain a bunch of base64 encoded ed25519 keys which aren't
+120
View File
@@ -0,0 +1,120 @@
# Conventional Commits
This project uses [Conventional
Commits](https://www.conventionalcommits.org/). Read the
[Summary](https://www.conventionalcommits.org/en/v1.0.0/#summary) or
the [Full
Specification](https://www.conventionalcommits.org/en/v1.0.0/#specification)
to learn more.
## Types
Conventional Commits defines _type_ (as in `type(scope):
message`). This section aims at listing the types used inside this
project:
| Type | Definition |
|-|-|
| `feat` | About a new feature. |
| `fix` | About a bug fix. |
| `test` | About a test (suite, case, runner…). |
| `doc` | About a documentation modification. |
| `refactor` | About a refactoring. |
| `ci` | About a Continuous Integration modification. |
| `chore` | About some cleanup, or regular tasks. |
## Scopes
Conventional Commits defines _scope_ (as in `type(scope): message`). This
section aims at listing all the scopes used inside this project:
<table>
<thead>
<tr>
<th>Group</th>
<th>Scope</th>
<th>Definition</th>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="10">Crates</td>
<td><code>sdk</code></td>
<td>About the <code>matrix-sdk</code> crate.</td>
</tr>
<tr>
<td><code>appservice</code></td>
<td>About the <code>matrix-sdk-appservice</code> crate.</td>
</tr>
<tr>
<td><code>base</code></td>
<td>About the <code>matrix-sdk-base</code> crate.</td>
</tr>
<tr>
<td><code>common</code></td>
<td>About the <code>matrix-sdk-common</code> crate.</td>
</tr>
<tr>
<td><code>crypto</code></td>
<td>About the <code>matrix-sdk-crypto</code> crate.</td>
</tr>
<tr>
<td><code>indexeddb</code></td>
<td>About the <code>matrix-sdk-indexeddb</code> crate.</td>
</tr>
<tr>
<td><code>qrcode</code></td>
<td>About the <code>matrix-sdk-qrcode</code> crate.</td>
</tr>
<tr>
<td><code>sled</code></td>
<td>About the <code>matrix-sdk-sled</code> crate.</td>
</tr>
<tr>
<td><code>store-encryption</code></td>
<td>About the <code>matrix-sdk-store-encryption</code> crate.</td>
</tr>
<tr>
<td><code>test</code></td>
<td>About the <code>matrix-sdk-test</code> and <code>matrix-sdk-test-macros</code> crate.</td>
</tr>
<tr>
<td rowspan="4">Bindings</td>
<td><code>apple</code></td>
<td>About the <code>matrix-rust-components-swift</code> binding.</td>
</tr>
<tr>
<td><code>crypto-nodejs</code></td>
<td>About the <code>matrix-sdk-crypto-nodejs</code> binding.</td>
</tr>
<tr>
<td><code>crypto-js</code></td>
<td>About the <code>matrix-sdk-crypto-js</code> binding.</td>
</tr>
<tr>
<td><code>crypto-ffi</code></td>
<td>About the <code>matrix-sdk-crypto-ffi</code> binding.</td>
</tr>
<tr>
<td>Labs</td>
<td><code>sled-state-inspector</code></td>
<td>About the <code>sled-state-inspector</code> project.</td>
</tr>
<tr>
<td>Continuous Integration</td>
<td><code>xtask</code></td>
<td>About the <code>xtask</code> project.</td>
</tr>
</tbody>
</table>
## Generating `CHANGELOG.md`
The [`git-cliff`](https://github.com/orhun/git-cliff) project is used
to generate `CHANGELOG.md` automatically. Hence the various
`cliff.toml` files that are present in this project, or the
`package.metadata.git-cliff` sections in various `Cargo.toml` files.
Its companion,
[`git-cliff-action`](https://github.com/orhun/git-cliff-action)
project, is used inside Github Action workflows.
Generated
+5377
View File
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -6,16 +6,24 @@ members = [
"bindings/matrix-sdk-crypto-nodejs",
"bindings/matrix-sdk-ffi",
"crates/*",
"testing/*",
"examples/*",
"labs/*",
"xtask",
]
# xtask, labs and the bindings should only be built when invoked explicitly.
# xtask, labs, testing and the bindings should only be built when invoked explicitly.
default-members = ["benchmarks", "crates/*"]
resolver = "2"
[profile.release]
lto = true
[profile.dev]
# Copied from rust-analyzer. Saves a lot of disk space and hopefully
# compilation time / mem usage too, at the expense of potentially having to
# change this setting here when you want to use a debugger.
debug = 0
[profile.dev.package]
# Optimize quote even in debug mode. Speeds up proc-macros enough to account
# for the extra time of optimizing it for a clean build of matrix-sdk-ffi.
-43
View File
@@ -1,43 +0,0 @@
# SDK 0.5 - stores and native crypto
The @matrix-org/rust team is happy to present to the wider public version 0.5 of the [`matrix-rust-sdk`][sdk], now available on crates.io for your convenience. This marks an important milestone after over half a year of work since the latest release (0.4) and with many new team members working on it. It comes with many bug fixes, improvements and updates, but a few we'd like to highlight here:
## Better, safer, native-er crypto
One of the biggest improvements under the hood is the replacement of the former crypto core written in C, libolm, with the fully rustic, fully audited [Vodozemac][vodozemac]. [Vodozemac][vodozemac] is a much more robust new implementation of the olm crypto primitives and engines with all the learning included and pitfalls from the previous C implementation avoided in a freshly baked, very robust library. With this release both matrix-sdk-crypto and matrix-sdk (when the `e2e-encryption` is enabled, which it is by default) use vodozemac to handle all crypto needs.
## Stores, stores, stores
This release has also seen a major refactoring around the state and crypto store primitives and implementations. From this release forward, the implementation of the storage layer is truly modular and pluggable, with the default implementation (based on `sled`) even being a separate crate. Furthermore this release has also additional support for the [`indexeddb`-storage][indexeddb] layer for in-browser WASM needs. As before, the base still ships with an in-memory store, but if none of them fits your needs, you are very much invited to build your own - we recommend looking at the implementation of the existing [`matrix-sdk-sled`][sled] to understand what is needed.
We've further extended and cleaned up the storage API and hardened the two existing implementations: both `sled` and `indexeddb` will now encrypt _all metadata_, including all keys, if a passphrase is given. Even a core dump of a database won't show any strings or other identifiable data. Both implementation also hold a database version string now, allowing future migrations of the database schema while we move forward.
## WebAssembly
It already came through in the previous paragraph: we now fully support `wasm` as a primary target for the main sdk and its dependencies (browser for now). While it was already possible to build the SDK for wasm before, if you were lucky and had the specific emscripten setup, even crypto didn't always fail to compile, our move to vodozemac frees us from these problems and our CI now makes sure all our PRs will continue to build on WebAssembly, too. And with the `indexeddb`-store, we also have a fully persistent storage layer implementation for all your in-browser matrix needs built in.
## More features
Of course a lot more has happened over the last few months as we've ramped up our work on the SDK. Most notably, you can now check whether a room has been created as a `space`, and we offer nice automatic thumbnailing and resizing support if you enable `image`. Additionally, there is a very early event-timeline API behind the `experimental-timeline`-feature-flag. Just to name a few. We really recommend you migrate from the older version to this one.
## Process and Workflow
We've also ramped up our CI, parallelized it a lot, while adding more tests. In general we don't merge anything breaking the tests, thus our `main` can also be considered `stable` to build again, though things might be changed without prior notice. We recommend keeping an eye out in our [team chat][chat] to stay current.
### Versions
This and all future releases of matrix-sdk adhere to [semver][semver] and we do our best to stick to it for the default-feature-set. As you will see only the main crate `matrix-sdk` is actually versioned at `0.5`, while other crates - especially newer ones - have different versions attached. We recommend to sticking to the high-level matrix-sdk-crate wherever reasonable as lower level crates might change more often, also in breaking fashion.
With this release all our crates are build with `rust v2021` and need a rust compiler of at least `1.60` (due to our need for weak-dependencies).
### Conventional Commit
We also want to make it easier for external parties to follow changes and stay up to date with what's-what, so, with this release, we are implementing the [conventional commits][cc] standard for our commits and will start putting in automatic changelog generation. Which makes it easier for us to create comprehensive changelogs for releases, but also for anyone following `main` to figure out what changed.
[sdk]: https://crates.io/crates/matrix-sdk/
[vodozemac]: https://github.com/matrix-org/vodozemac
[sled]: https://crates.io/crates/matrix-sdk-sled/
[indexeddb]: https://crates.io/crates/matrix-sdk-indexeddb/
[chat]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
[semver]: https://semver.org/
[cc]: http://conventionalcommits.org/
+121
View File
@@ -0,0 +1,121 @@
# Upgrades 0.5 ➜ 0.6
This is a rough migration guide to help you upgrade your code using matrix-sdk 0.5 to the newly released matrix-sdk 0.6 . While it won't cover all edge cases and problems, we are trying to get the most common issues covered. If you experience any other difficulties in upgrade or need support with using the matrix-sdk in general, please approach us in our [matrix-sdk channel on matrix.org][matrix-channel].
## Minimum Supported Rust Version Update: `1.60`
We have updated the minimal rust version you need in order to build `matrix-sdk`, as we require some new dependency resolving features from it:
> These crates are built with the Rust language version 2021 and require a minimum compiler version of 1.60
## Dependencies
Many dependencies have been upgraded. Most notably, we are using `ruma` at version `0.7.0` now. It has seen some renamings and restructurings since our last release, so you might find that some Types have new names now.
## Repo Structure Updates
If you are looking at the repository itself, you will find we've rearranged the code quite a bit: we have split out any bindings-specific and testing related crates (and other things) into respective folders, and we've moved all `examples` into its own top-level-folder with each example as their own crate (rendering them easier to find and copy as starting points), all in all slimming down the `crates` folder to the core aspects.
## Architecture Changes / API overall
### Builder Pattern
We are moving to the [builder pattern][] (familiar from e.g. `std::io:process:Command`) as the main configurable path for many aspects of the API, including to construct Matrix-Requests and workflows. This has been and is an on-going effort, and this release sees a lot of APIs transitioning to this pattern, you should already be familiar with from the `matrix_sdk::Client::builder()` in `0.5`. This pattern been extended onto:
- the [login configuration][login builder] and [login with sso][ssologin builder],
- [`SledStore` configuratiion][sled-store builder]
- [`Indexeddb` configuration][indexeddb builder]
Most have fallback (though maybe with deprecation warning) support for an existing code path, but these are likely to be removed in upcoming releases.
### Splitting of concerns: Media
In an effort to declutter the `Client` API dedicated types have been created dealing with specific concerns in one place. In `0.5` we introduced `client.account()`, and `client.encryption()`, we are doing the same with `client.media()` to manage media and attachments in one place with the [`media::Media` type][media typ] now.
The signatures of media uploads, have also changed slightly: rather than expecting a reader `R: Read + Seek`, it now is a simple `&[u8]`. Which also means no more unnecessary `seek(0)` to reset the cursor, as we are just taking an immutable reference now.
### Event Handling & sync updaes
If you are using the `client.register_event_handler` function to receive updates on incoming sync events, you'll find yourself with a deprecation warning now. That is because we've refactored and redesigned the event handler logic to allowing `removing` of event handlers on the fly, too. For that the new `add_event_handler()` (and `add_room_event_handler`) will hand you an `EventHandlerHandle` (pardon the pun), which you can pass to `remove_event_handler`, or by using the convenient `client.event_handler_drop_guard` to create a `DropGuard` that will remove the handler when the guard is dropped. While the code still works, we recommend you switch to the new one, as we will be removing the `register_event_handler` and `register_event_handler_context` in a coming release.
Secondly, you will find a new [`sync_with_result_callback` sync function][sync with result]. Other than the previous sync functions, this will pass the entire `Result` to your callback, allowing you to handle errors or even raise some yourself to stop the loop. Further more, it will propagate any unhandled errors (it still handles retries as before) to the outer caller, allowing the higher level to decide how to handle that (e.g. in case of a network failure). This result-returning-behavior also punshes through the existing `sync` and `sync_with_callback`-API, allowing you to handle them on a higher level now (rather than the futures just resolving). If you find that warning, just adding a `?` to the `.await` of the call is probably the quickest way to move forward.
### Refresh Tokens
This release now [supports `refresh_token`s][refresh tokens PR] as part of the [`Session`][session]. It is implemented with a default-flag in serde so deserializing a previously serialized Session (e.g. in a store) will work as before. As part of `refresh_token` support, you can now configure the client via `ClientBuilder.request_refresh_token()` to refresh the access token automagically on certain failures or do it manually by calling `client.refresh_access_token()` yourself. Auto-refresh is _off_ by default.
You can stay informed about updates on the access token by listening to `client.session_tokens_signal()`.
### Further changes
- [`MessageOptions`][message options] has been updated to Matrix 1.3 by making the `from` parameter optional (and function signatures have been updated, too). You can now request the server sends you messages from the first one you are allowed to have received.
- `client.user_id()` is not a `future` anymore. Remove any `.await` you had behind it.
- `verified()`, `blacklisted()` and `deleted()` on `matrix_sdk::encryption::identities::Device` have been renamed with a `is_` prefix.
- `verified()` on `matrix_sdk::encryption::identities::UserIdentity`, too has been prefixed with `is_` and thus is onw called `is_verified()`.
- The top-level crypto and state-store types of Indexeddb and Sled have been renamed to unique types>
- `state_store` and `crypto_store` do not need to be boxed anymore when passed to the [`StoreConfig`][store config]
- Indexeddb's `SerializationError` is now `IndexedDBStoreError`
- Javascript specific features are now behind the `js` feature-gate
- The new experimental next generation of sync ("sliding sync"), with a totally revamped api, can be found behind the optional `sliding-sync`-feature-gate
## Quick Troubleshooting
You find yourself focussed with any of these, here are the steps to follow to upgrade your code accordingly:
### warning: use of deprecated associated function `matrix_sdk::Client::register_event_handler`: Use [`Client::add_event_handler`](#method.add_event_handler) instead
As it says on the tin: we have deprecated this function in favor of the newer removable handler approach (see above). You can still continue to use this `fn` for now, but it will be removed in a future release.
### warning: use of deprecated associated function `matrix_sdk::Client::login`: Replaced by [`Client::login_username`](#method.login_username)
We have replaced the login facilities with a `LoginBuilder` and recommend you use that from now on. This isn't an error yet, but the function will be removed in a future release.
### expected slice `[u8]`, found struct ...
We've updated the `send_attachment` and `Media` signatures to use `&[u8]` rather than `reader: Read + Seek` as it is more convenient and common place for most architectures anyways. If you are using `File::open(path)?` to get that handler, you can just replace that with `std::fs::read(path)?`
### no method named `verified` found for struct `matrix_sdk::encryption::identities::Device` in the current scope
Boolean flags like `verified`, `deleted`, `blacklisted`, etc have been renamed with a `is_` prefix. So, just follow the cargo suggestion:
```
|
69 | device.verified()
| ^^^^^^^^ help: there is an associated function with a similar name: `is_verified`
```
### unresolved import `matrix_sdk::ruma::events::AnySyncRoomEvent`
Ruma has been updated to `0.7.0`, you will find some ruma Events names have changed, most notably, the `AnySyncRoomEvent` is now named `AnySyncTimelineEvent` (and not `AnySyncStateEvent`, which cargo wrongly suggests). Just rename the import and usage of it.
### `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
You are seeing something along the lines of:
```
19 | if room_member.state_key != client.user_id().await.unwrap() {
| ^^^^^^ `std::option::Option<&matrix_sdk::ruma::UserId>` is not a future
|
= help: the trait `Future` is not implemented for `std::option::Option<&matrix_sdk::ruma::UserId>`
= note: std::option::Option<&matrix_sdk::ruma::UserId> must be a future or must implement `IntoFuture` to be awaited
= note: required because of the requirements on the impl of `IntoFuture` for `std::option::Option<&matrix_sdk::ruma::UserId>`
help: remove the `.await`
|
19 - if room_member.state_key != client.user_id().await.unwrap() {
19 + if room_member.state_key != client.user_id().unwrap() {
```
You are using `client.user_id().await` but `user_id()` is no longer `async`. Just follow the cargo suggestion and remove the `.await`, it is not necessary any longer.
[matrix-channel]: https://matrix.to/#/#matrix-rust-sdk:matrix.org
[builder pattern]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
[login builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.LoginBuilder.html
[ssologin builder]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.SsoLoginBuilder.html
[sled-store builder]: https://docs.rs/matrix-sdk-sled/latest/matrix_sdk_sled/struct.SledStateStoreBuilder.html
[indexeddb builder]: https://docs.rs/matrix-sdk-indexeddb/latest/matrix_sdk_indexeddb/struct.IndexeddbStateStoreBuilder.html
[media type]: https://docs.rs/matrix-sdk/latest/matrix_sdk//media/struct.Media.html
[sync with result]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Client.html#method.sync_with_result_callback
[session]: https://docs.rs/matrix-sdk/latest/matrix_sdk/struct.Session.html
[refresh tokens PR]: https://github.com/matrix-org/matrix-rust-sdk/pull/892
[store config]: https://docs.rs/matrix-sdk-base/latest/matrix_sdk_base/store/struct.StoreConfig.html
[message options]: https://docs.rs/matrix-sdk/latest/matrix_sdk/room/struct.MessagesOptions.html
+5 -5
View File
@@ -9,16 +9,16 @@ publish = false
[dependencies]
criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_reports"] }
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.5.0" }
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.1.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../crates/matrix-sdk-test", version = "0.5.0" }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f" }
matrix-sdk-crypto = { path = "../crates/matrix-sdk-crypto", version = "0.6.0"}
matrix-sdk-sled = { path = "../crates/matrix-sdk-sled", version = "0.2.0", default-features = false, features = ["crypto-store"] }
matrix-sdk-test = { path = "../testing/matrix-sdk-test", version = "0.6.0"}
ruma = "0.7.0"
serde_json = "1.0.79"
tempfile = "3.3.0"
tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] }
[target.'cfg(target_os = "linux")'.dependencies]
pprof = { version = "0.8.0", features = ["flamegraph", "criterion"] }
pprof = { version = "0.10.0", features = ["flamegraph", "criterion"] }
[[bench]]
name = "crypto_bench"
+5 -5
View File
@@ -2,7 +2,7 @@ use std::{ops::Deref, sync::Arc};
use criterion::*;
use matrix_sdk_crypto::{EncryptionSettings, OlmMachine};
use matrix_sdk_sled::CryptoStore as SledCryptoStore;
use matrix_sdk_sled::SledCryptoStore;
use matrix_sdk_test::response_from_file;
use ruma::{
api::{
@@ -63,7 +63,7 @@ pub fn keys_query(c: &mut Criterion) {
let mut group = c.benchmark_group("Keys querying");
group.throughput(Throughput::Elements(count as u64));
let name = format!("{} device and cross signing keys", count);
let name = format!("{count} device and cross signing keys");
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
b.to_async(&runtime)
@@ -96,7 +96,7 @@ pub fn keys_claiming(c: &mut Criterion) {
let mut group = c.benchmark_group("Olm session creation");
group.throughput(Throughput::Elements(count as u64));
let name = format!("{} one-time keys", count);
let name = format!("{count} one-time keys");
group.bench_with_input(BenchmarkId::new("memory store", &name), &response, |b, response| {
b.iter_batched(
@@ -158,7 +158,7 @@ pub fn room_key_sharing(c: &mut Criterion) {
let mut group = c.benchmark_group("Room key sharing");
group.throughput(Throughput::Elements(count as u64));
let name = format!("{} devices", count);
let name = format!("{count} devices");
group.bench_function(BenchmarkId::new("memory store", &name), |b| {
b.to_async(&runtime).iter(|| async {
@@ -225,7 +225,7 @@ pub fn devices_missing_sessions_collecting(c: &mut Criterion) {
let mut group = c.benchmark_group("Devices missing sessions collecting");
group.throughput(Throughput::Elements(count as u64));
let name = format!("{} devices", count);
let name = format!("{count} devices");
runtime.block_on(machine.mark_request_as_sent(&txn_id, &response)).unwrap();
@@ -1,513 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 55;
objects = {
/* Begin PBXBuildFile section */
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; };
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 189A89B927B40BBF0048B0A5 /* sdk.swift */; };
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */; };
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89D927B2939900CA89E1 /* ContentView.swift */; };
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 18CE89DB27B2939A00CA89E1 /* Assets.xcassets */; };
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 18CE89CC27B2939900CA89E1 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 18CE89D327B2939900CA89E1;
remoteInfo = MatrixRustSDK;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
181AA19C27B52AB40005F102 /* MatrixSDKFFI.xcframework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
181AA19927B52AA60005F102 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = MatrixSDKFFI.xcframework; path = ../../generated/MatrixSDKFFI.xcframework; sourceTree = "<group>"; };
189A89B927B40BBF0048B0A5 /* sdk.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = sdk.swift; path = ../../../generated/swift/sdk.swift; sourceTree = "<group>"; };
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MatrixRustSDK-Bridging-Header.h"; sourceTree = "<group>"; };
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MatrixRustSDK.app; sourceTree = BUILT_PRODUCTS_DIR; };
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKApp.swift; sourceTree = "<group>"; };
18CE89D927B2939900CA89E1 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MatrixRustSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixRustSDKTests.swift; sourceTree = "<group>"; };
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = MatrixRustSDK.entitlements; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
18CE89D127B2939900CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
181AA19B27B52AB40005F102 /* MatrixSDKFFI.xcframework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E127B2939A00CA89E1 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
189A89AB27B2E16B0048B0A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
181AA19A27B52AB40005F102 /* MatrixSDKFFI.xcframework */,
);
name = Frameworks;
sourceTree = "<group>";
};
189A89B827B40BB10048B0A5 /* Generated */ = {
isa = PBXGroup;
children = (
189A89B927B40BBF0048B0A5 /* sdk.swift */,
);
name = Generated;
sourceTree = "<group>";
};
18CE89CB27B2939900CA89E1 = {
isa = PBXGroup;
children = (
18CE89D627B2939900CA89E1 /* MatrixRustSDK */,
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */,
18CE89D527B2939900CA89E1 /* Products */,
189A89AB27B2E16B0048B0A5 /* Frameworks */,
);
sourceTree = "<group>";
};
18CE89D527B2939900CA89E1 /* Products */ = {
isa = PBXGroup;
children = (
18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */,
18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
18CE89D627B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXGroup;
children = (
181AA19927B52AA60005F102 /* Info.plist */,
189A89B827B40BB10048B0A5 /* Generated */,
18CE89D727B2939900CA89E1 /* MatrixRustSDKApp.swift */,
18CE89D927B2939900CA89E1 /* ContentView.swift */,
18CE8A0127B293A900CA89E1 /* MatrixRustSDK.entitlements */,
18CE89DB27B2939A00CA89E1 /* Assets.xcassets */,
189A89C327B417CA0048B0A5 /* MatrixRustSDK-Bridging-Header.h */,
);
path = MatrixRustSDK;
sourceTree = "<group>";
};
18CE89E727B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXGroup;
children = (
18CE89E827B2939A00CA89E1 /* MatrixRustSDKTests.swift */,
);
path = MatrixRustSDKTests;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
18CE89D327B2939900CA89E1 /* MatrixRustSDK */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */;
buildPhases = (
18CE89D027B2939900CA89E1 /* Sources */,
18CE89D127B2939900CA89E1 /* Frameworks */,
18CE89D227B2939900CA89E1 /* Resources */,
18CE8A1F27B2941600CA89E1 /* Embed Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = MatrixRustSDK;
productName = MatrixRustSDK;
productReference = 18CE89D427B2939900CA89E1 /* MatrixRustSDK.app */;
productType = "com.apple.product-type.application";
};
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */;
buildPhases = (
18CE89E027B2939A00CA89E1 /* Sources */,
18CE89E127B2939A00CA89E1 /* Frameworks */,
18CE89E227B2939A00CA89E1 /* Resources */,
);
buildRules = (
);
dependencies = (
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */,
);
name = MatrixRustSDKTests;
productName = MatrixRustSDKTests;
productReference = 18CE89E427B2939A00CA89E1 /* MatrixRustSDKTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
18CE89CC27B2939900CA89E1 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1320;
LastUpgradeCheck = 1320;
TargetAttributes = {
18CE89D327B2939900CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
LastSwiftMigration = 1320;
};
18CE89E327B2939A00CA89E1 = {
CreatedOnToolsVersion = 13.2.1;
TestTargetID = 18CE89D327B2939900CA89E1;
};
};
};
buildConfigurationList = 18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */;
compatibilityVersion = "Xcode 13.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 18CE89CB27B2939900CA89E1;
productRefGroup = 18CE89D527B2939900CA89E1 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
18CE89D327B2939900CA89E1 /* MatrixRustSDK */,
18CE89E327B2939A00CA89E1 /* MatrixRustSDKTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
18CE89D227B2939900CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DC27B2939A00CA89E1 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E227B2939A00CA89E1 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
18CE89D027B2939900CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89DA27B2939900CA89E1 /* ContentView.swift in Sources */,
189A89BA27B40BBF0048B0A5 /* sdk.swift in Sources */,
18CE89D827B2939900CA89E1 /* MatrixRustSDKApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
18CE89E027B2939A00CA89E1 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
18CE89E927B2939A00CA89E1 /* MatrixRustSDKTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
18CE89E627B2939A00CA89E1 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 18CE89D327B2939900CA89E1 /* MatrixRustSDK */;
targetProxy = 18CE89E527B2939A00CA89E1 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
18CE89F627B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
18CE89F727B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
18CE89F927B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
18CE89FA27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = MatrixRustSDK/MatrixRustSDK.entitlements;
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "-";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = MatrixRustSDK/Info.plist;
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDK;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTS_MACCATALYST = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OBJC_BRIDGING_HEADER = "MatrixRustSDK/MatrixRustSDK-Bridging-Header.h";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
18CE89FC27B2939A00CA89E1 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Debug;
};
18CE89FD27B2939A00CA89E1 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = org.matrix.MatrixRustSDKTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MatrixRustSDK.app/MatrixRustSDK";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
18CE89CF27B2939900CA89E1 /* Build configuration list for PBXProject "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F627B2939A00CA89E1 /* Debug */,
18CE89F727B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89F827B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDK" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89F927B2939A00CA89E1 /* Debug */,
18CE89FA27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
18CE89FB27B2939A00CA89E1 /* Build configuration list for PBXNativeTarget "MatrixRustSDKTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
18CE89FC27B2939A00CA89E1 /* Debug */,
18CE89FD27B2939A00CA89E1 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 18CE89CC27B2939900CA89E1 /* Project object */;
}
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1320"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89E327B2939A00CA89E1"
BuildableName = "MatrixRustSDKTests.xctest"
BlueprintName = "MatrixRustSDKTests"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89ED27B2939A00CA89E1"
BuildableName = "MatrixRustSDKUITests.xctest"
BlueprintName = "MatrixRustSDKUITests"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "18CE89D327B2939900CA89E1"
BuildableName = "MatrixRustSDK.app"
BlueprintName = "MatrixRustSDK"
ReferencedContainer = "container:MatrixRustSDK.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -1,11 +0,0 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,98 +0,0 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,21 +0,0 @@
//
// ContentView.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, Rust!")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
-5
View File
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
</plist>
@@ -1,5 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "sdkFFI.h"
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>
@@ -1,17 +0,0 @@
//
// MatrixRustSDKApp.swift
// MatrixRustSDK
//
// Created by Stefan Ceriu on 08.02.2022.
//
import SwiftUI
@main
struct MatrixRustSDKApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@@ -1,39 +0,0 @@
//
// MatrixRustSDKTests.swift
// MatrixRustSDKTests
//
// Created by Stefan Ceriu on 08.02.2022.
//
import XCTest
@testable import MatrixRustSDK
class MatrixRustSDKTests: XCTestCase {
func testReadOnlyFileSystemError() {
do {
let client = try ClientBuilder()
.basePath(path: "")
.username(username: "@test:domain")
.build()
try client.login(username: "@test:domain", password: "test")
} catch ClientError.Generic(let message) {
XCTAssertNotNil(message.range(of: "Read-only file system"))
} catch {
XCTFail("Not expecting any other kind of exception")
}
}
// MARK: - Private
static private var basePath: String {
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError("Should always be able to retrieve the caches directory")
}
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
}
+21
View File
@@ -0,0 +1,21 @@
// swift-tools-version: 5.6
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "MatrixRustSDK",
platforms: [.iOS(.v15)],
products: [
.library(name: "MatrixRustSDK",
targets: ["MatrixRustSDK"]),
],
targets: [
.binaryTarget(name: "MatrixSDKFFI", path: "generated/MatrixSDKFFI.xcframework"),
.target(name: "MatrixRustSDK",
dependencies: [.target(name: "MatrixSDKFFI")],
path: "generated/swift"),
.testTarget(name: "MatrixRustSDKTests",
dependencies: ["MatrixRustSDK"]),
]
)
@@ -0,0 +1,50 @@
import XCTest
@testable import MatrixRustSDK
final class ClientTests: XCTestCase {
func testBuildingWithHomeserverURL() {
do {
_ = try ClientBuilder()
.homeserverUrl(url: "https://localhost:8008")
.build()
} catch {
XCTFail("The client should build successfully when given a homeserver.")
}
}
func testBuildingWithUsername() {
do {
_ = try ClientBuilder()
.username(username: "@test:matrix.org")
.build()
} catch {
XCTFail("The client should build successfully when given a username.")
}
}
func testBuildingWithInvalidUsername() {
do {
_ = try ClientBuilder()
.username(username: "@test:invalid")
.build()
XCTFail("The client should not build when given an invalid username.")
} catch ClientError.Generic(let message) {
XCTAssertTrue(message.contains(".well-known"), "The client should fail to do the well-known lookup.")
} catch {
XCTFail("Not expecting any other kind of exception")
}
}
// MARK: - Private
static private var basePath: String {
guard let url = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
fatalError("Should always be able to retrieve the caches directory")
}
try? FileManager.default.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
return url.path
}
}
+16 -15
View File
@@ -17,20 +17,22 @@ REL_TYPE_DIR="release"
# Build static libs for all the different architectures
# iOS
echo -e "Building for iOS [1/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios"
# MacOS
echo -e "\nBuilding for macOS (Apple Silicon) [2/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin"
echo -e "\nBuilding for macOS (Intel) [3/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin"
# iOS Simulator
echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
echo -e "\nBuilding for iOS Simulator (Intel) [5/5]"
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
# Mac Catalyst
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-macabi"
cargo +nightly build -Z build-std -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios-macabi"
echo -e "\nCreating XCFramework"
# Lipo together the libraries for the same platform
# MacOS
@@ -45,21 +47,23 @@ lipo -create \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
# Mac Catalyst
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-macabi/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
# Architecture for the .a file argument doesn't matter, since the API is the same on all
uniffi-bindgen generate \
--language swift \
--lib-file "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
--out-dir ${GENERATED_DIR} \
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
# Rename and move modulemap to the right place
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
@@ -75,8 +79,6 @@ xcodebuild -create-xcframework \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \
-headers ${HEADERS_DIR} \
-library "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" \
-headers ${HEADERS_DIR} \
-library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-headers ${HEADERS_DIR} \
-output "${GENERATED_DIR}/MatrixSDKFFI.xcframework"
@@ -85,5 +87,4 @@ xcodebuild -create-xcframework \
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_maccatalyst.a"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
+33 -14
View File
@@ -4,42 +4,63 @@ set -eEu
cd "$(dirname "$0")"
IS_CI=false
ACTIVE_ARCH="arm64"
if [ $# -eq 1 ]; then
IS_CI=true
if [ $# -eq 2 ]; then
echo "Running CI build"
IS_CI=true
ARCHS=( $1 )
ACTIVE_ARCH=${ARCHS[0]}
elif [ $# -eq 1 ]; then
echo "Running debug build"
ARCHS=( $1 )
ACTIVE_ARCH=${ARCHS[0]}
else
echo "Running debug build"
fi
echo "Active architecture ${ACTIVE_ARCH}"
# Path to the repo root
SRC_ROOT=../..
TARGET_DIR="${SRC_ROOT}/target"
GENERATED_DIR="${SRC_ROOT}/generated"
GENERATED_DIR="${SRC_ROOT}/bindings/apple/generated"
mkdir -p ${GENERATED_DIR}
REL_FLAG=""
REL_TYPE_DIR="debug"
# iOS Simulator
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim"
cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios"
# iOS Simulator arm64
if [ "$ACTIVE_ARCH" = "arm64" ]; then
TARGET="aarch64-apple-ios-sim"
# iOS Simulator intel
else
TARGET="x86_64-apple-ios"
fi
cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "$TARGET"
lipo -create \
"${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
"${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
-output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"
# Generate uniffi files
uniffi-bindgen generate "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" --language swift --out-dir ${GENERATED_DIR}
uniffi-bindgen generate \
--language swift \
--lib-file "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \
--out-dir ${GENERATED_DIR} \
"${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl"
# Move them to the right place
HEADERS_DIR=${GENERATED_DIR}/headers
mkdir -p ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}
mv ${GENERATED_DIR}/*.h ${HEADERS_DIR}
# Rename and move modulemap to the right place
mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap
SWIFT_DIR="${GENERATED_DIR}/swift"
mkdir -p ${SWIFT_DIR}
@@ -57,15 +78,13 @@ xcodebuild -create-xcframework \
# Cleanup
# if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
# if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi
if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi
if [ "$IS_CI" = false ] ; then
echo "Preparing matrix-rust-components-swift"
# Debug -> Copy generated files over to ../../../matrix-rust-components-swift
echo "$(printf "import MatrixSDKFFIWrapper\n\n"; cat "${SWIFT_DIR}/sdk.swift")" > "${SWIFT_DIR}/sdk.swift"
rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/"
rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK"
fi
+8 -9
View File
@@ -2,7 +2,7 @@
name = "matrix-sdk-crypto-ffi"
version = "0.1.0"
authors = ["Damir Jelić <poljar@termina.org.uk>"]
edition = "2018"
edition = "2021"
rust-version = "1.60"
description = "Uniffi based bindings for the Rust SDK crypto crate"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
@@ -20,7 +20,7 @@ hmac = "0.12.1"
http = "0.2.6"
pbkdf2 = "0.11.0"
rand = "0.8.5"
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c"] }
ruma = { version = "0.7.0", features = ["client-api-c"] }
serde = "1.0.136"
serde_json = "1.0.79"
sha2 = "0.10.2"
@@ -28,7 +28,7 @@ thiserror = "1.0.30"
tracing = "0.1.34"
tracing-subscriber = { version = "0.3.11", features = ["env-filter"] }
# keep in sync with uniffi dependency in matrix-sdk-ffi, and uniffi_bindgen in ffi CI job
uniffi = "0.18.0"
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
zeroize = { version = "1.3.0", features = ["zeroize_derive"] }
[dependencies.js_int]
@@ -37,16 +37,16 @@ features = ["lax_deserialize"]
[dependencies.matrix-sdk-common]
path = "../../crates/matrix-sdk-common"
version = "0.5.0"
version = "0.6.0"
[dependencies.matrix-sdk-crypto]
path = "../../crates/matrix-sdk-crypto"
version = "0.5.0"
version = "0.6.0"
features = ["qrcode", "backups_v1"]
[dependencies.matrix-sdk-sled]
path = "../../crates/matrix-sdk-sled"
version = "0.1.0"
version = "0.2.0"
default_features = false
features = ["crypto-store"]
@@ -56,11 +56,10 @@ default_features = false
features = ["rt-multi-thread"]
[dependencies.vodozemac]
git = "https://github.com/matrix-org/vodozemac/"
rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd"
version = "0.3.0"
[build-dependencies]
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
[dev-dependencies]
tempfile = "3.3.0"
+15 -7
View File
@@ -14,7 +14,7 @@ mod responses;
mod users;
mod verification;
use std::{borrow::Borrow, collections::HashMap, convert::TryFrom, str::FromStr, sync::Arc};
use std::{borrow::Borrow, collections::HashMap, str::FromStr, sync::Arc};
pub use backup_recovery_key::{
BackupRecoveryKey, DecodeError, MegolmV1BackupKey, PassphraseInfo, PkDecryptionError,
@@ -26,6 +26,7 @@ pub use error::{
use js_int::UInt;
pub use logger::{set_logger, Logger};
pub use machine::{KeyRequestPair, OlmMachine};
use matrix_sdk_crypto::types::{EventEncryptionAlgorithm, SigningKey};
pub use responses::{
BootstrapCrossSigningResult, DeviceLists, KeysImportResult, OutgoingVerificationRequest,
Request, RequestType, SignatureUploadRequest, UploadSigningKeysRequest,
@@ -161,7 +162,7 @@ pub fn migrate(
olm::PrivateCrossSigningIdentity,
store::{Changes as RustChanges, CryptoStore, RecoveryKey},
};
use matrix_sdk_sled::CryptoStore as SledStore;
use matrix_sdk_sled::SledCryptoStore;
use tokio::runtime::Runtime;
use vodozemac::{
megolm::InboundGroupSession,
@@ -184,7 +185,7 @@ pub fn migrate(
progress_listener.on_progress(progress as i32, total as i32)
};
let store = SledStore::open_with_passphrase(path, passphrase.as_deref())?;
let store = SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())?;
let runtime = Runtime::new()?;
processed_steps += 1;
@@ -249,19 +250,26 @@ pub fn migrate(
let pickle =
InboundGroupSession::from_libolm_pickle(&session.pickle, &data.pickle_key)?.pickle();
let sender_key = Curve25519PublicKey::from_base64(&session.sender_key)?;
let pickle = matrix_sdk_crypto::olm::PickledInboundGroupSession {
pickle,
sender_key: session.sender_key,
sender_key,
signing_key: session
.signing_key
.into_iter()
.map(|(k, v)| Ok((DeviceKeyAlgorithm::try_from(k)?, v)))
.map(|(k, v)| {
let algorithm = DeviceKeyAlgorithm::try_from(k)?;
let key = SigningKey::from_parts(&algorithm, v)?;
Ok((algorithm, key))
})
.collect::<anyhow::Result<_>>()?,
room_id: RoomId::parse(session.room_id)?,
forwarding_chains: session.forwarding_chains,
imported: session.imported,
backed_up: session.backed_up,
history_visibility: None,
algorithm: EventEncryptionAlgorithm::MegolmV1AesSha2,
};
let session = matrix_sdk_crypto::olm::InboundGroupSession::from_pickle(pickle)?;
@@ -575,7 +583,7 @@ mod test {
"JGgPQRuYj3ScMdPS+A0P+k/1qS9Hr3qeKXLscI+hS78"
);
let room_keys = machine.runtime.block_on(machine.inner.export_keys(|_| true))?;
let room_keys = machine.runtime.block_on(machine.inner.export_room_keys(|_| true))?;
assert_eq!(room_keys.len(), 2);
let cross_signing_status = machine.cross_signing_status();
+34 -35
View File
@@ -1,6 +1,5 @@
use std::{
collections::{BTreeMap, HashMap},
convert::TryInto,
io::Cursor,
ops::Deref,
sync::Arc,
@@ -11,7 +10,7 @@ use base64::{decode_config, encode, STANDARD_NO_PAD};
use js_int::UInt;
use matrix_sdk_common::deserialized_responses::AlgorithmInfo;
use matrix_sdk_crypto::{
backups::MegolmV1BackupKey as RustBackupKey, decrypt_key_export, encrypt_key_export,
backups::MegolmV1BackupKey as RustBackupKey, decrypt_room_key_export, encrypt_room_key_export,
matrix_sdk_qrcode::QrVerificationData, olm::ExportedRoomKey, store::RecoveryKey,
EncryptionSettings, LocalTrust, OlmMachine as InnerMachine, UserIdentities,
Verification as RustVerification,
@@ -32,10 +31,8 @@ use ruma::{
},
IncomingResponse,
},
events::{
key::verification::VerificationMethod, room::encrypted::OriginalSyncRoomEncryptedEvent,
AnySyncMessageLikeEvent,
},
events::{key::verification::VerificationMethod, AnySyncMessageLikeEvent},
serde::Raw,
DeviceKeyAlgorithm, EventId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
};
use serde::{Deserialize, Serialize};
@@ -95,7 +92,7 @@ impl OlmMachine {
let runtime = Runtime::new().expect("Couldn't create a tokio runtime");
let store = Arc::new(
matrix_sdk_sled::CryptoStore::open_with_passphrase(path, passphrase.as_deref())
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(path, passphrase.as_deref())
.map_err(|e| {
match e {
// This is a bit of an error in the sled store, the
@@ -176,7 +173,7 @@ impl OlmMachine {
{
match identity {
UserIdentities::Own(i) => i.is_verified(),
UserIdentities::Other(i) => i.verified(),
UserIdentities::Other(i) => i.is_verified(),
}
} else {
false
@@ -602,7 +599,7 @@ impl OlmMachine {
content: &'a RawValue,
}
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = RoomId::parse(room_id)?;
let decrypted = self.runtime.block_on(self.inner.decrypt_room_event(&event, &room_id))?;
@@ -613,16 +610,16 @@ impl OlmMachine {
let event_json: Event<'_> = serde_json::from_str(decrypted.event.json().get())?;
Ok(match &encryption_info.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 {
curve25519_key,
sender_claimed_keys,
forwarding_curve25519_key_chain,
} => DecryptedEvent {
clear_event: serde_json::to_string(&event_json)?,
sender_curve25519_key: curve25519_key.to_owned(),
claimed_ed25519_key: sender_claimed_keys.get(&DeviceKeyAlgorithm::Ed25519).cloned(),
forwarding_curve25519_chain: forwarding_curve25519_key_chain.to_owned(),
},
AlgorithmInfo::MegolmV1AesSha2 { curve25519_key, sender_claimed_keys } => {
DecryptedEvent {
clear_event: serde_json::to_string(&event_json)?,
sender_curve25519_key: curve25519_key.to_owned(),
claimed_ed25519_key: sender_claimed_keys
.get(&DeviceKeyAlgorithm::Ed25519)
.cloned(),
forwarding_curve25519_chain: vec![],
}
}
})
}
@@ -640,7 +637,7 @@ impl OlmMachine {
event: &str,
room_id: &str,
) -> Result<KeyRequestPair, DecryptionError> {
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = RoomId::parse(room_id)?;
let (cancel, request) =
@@ -661,16 +658,20 @@ impl OlmMachine {
///
/// * `rounds` - The number of rounds that should be used when expanding the
/// passphrase into an key.
pub fn export_keys(&self, passphrase: &str, rounds: i32) -> Result<String, CryptoStoreError> {
let keys = self.runtime.block_on(self.inner.export_keys(|_| true))?;
pub fn export_room_keys(
&self,
passphrase: &str,
rounds: i32,
) -> Result<String, CryptoStoreError> {
let keys = self.runtime.block_on(self.inner.export_room_keys(|_| true))?;
let encrypted = encrypt_key_export(&keys, passphrase, rounds as u32)
let encrypted = encrypt_room_key_export(&keys, passphrase, rounds as u32)
.map_err(CryptoStoreError::Serialization)?;
Ok(encrypted)
}
fn import_keys_helper(
fn import_room_keys_helper(
&self,
keys: Vec<ExportedRoomKey>,
from_backup: bool,
@@ -680,7 +681,8 @@ impl OlmMachine {
progress_listener.on_progress(progress as i32, total as i32)
};
let result = self.runtime.block_on(self.inner.import_keys(keys, from_backup, listener))?;
let result =
self.runtime.block_on(self.inner.import_room_keys(keys, from_backup, listener))?;
Ok(KeysImportResult {
imported: result.imported_count as i64,
@@ -708,20 +710,20 @@ impl OlmMachine {
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the key import.
pub fn import_keys(
pub fn import_room_keys(
&self,
keys: &str,
passphrase: &str,
progress_listener: Box<dyn ProgressListener>,
) -> Result<KeysImportResult, KeyImportError> {
let keys = Cursor::new(keys);
let keys = decrypt_key_export(keys, passphrase)?;
self.import_keys_helper(keys, false, progress_listener)
let keys = decrypt_room_key_export(keys, passphrase)?;
self.import_room_keys_helper(keys, false, progress_listener)
}
/// Import room keys from the given serialized unencrypted key export.
///
/// This method is the same as [`OlmMachine::import_keys`] but the
/// This method is the same as [`OlmMachine::import_room_keys`] but the
/// decryption step is skipped and should be performed by the caller. This
/// should be used if the room keys are coming from the server-side backup,
/// the method will mark all imported room keys as backed up.
@@ -732,7 +734,7 @@ impl OlmMachine {
///
/// * `progress_listener` - A callback that can be used to introspect the
/// progress of the key import.
pub fn import_decrypted_keys(
pub fn import_decrypted_room_keys(
&self,
keys: &str,
progress_listener: Box<dyn ProgressListener>,
@@ -741,7 +743,7 @@ impl OlmMachine {
let keys = keys.into_iter().map(serde_json::from_value).filter_map(|k| k.ok()).collect();
self.import_keys_helper(keys, true, progress_listener)
self.import_room_keys_helper(keys, true, progress_listener)
}
/// Discard the currently active room key for the given room if there is
@@ -1243,10 +1245,7 @@ impl OlmMachine {
) -> Option<OutgoingVerificationRequest> {
let user_id = UserId::parse(user_id).ok()?;
self.inner
.get_verification(&user_id, flow_id)
.and_then(|s| s.sas_v1())
.and_then(|s| s.accept().map(|r| r.into()))
self.inner.get_verification(&user_id, flow_id)?.sas_v1()?.accept().map(|r| r.into())
}
/// Get a list of emoji indices of the emoji representation of the short
+3 -3
View File
@@ -358,15 +358,15 @@ interface OlmMachine {
KeyRequestPair request_room_key([ByRef] string event, [ByRef] string room_id);
[Throws=CryptoStoreError]
string export_keys([ByRef] string passphrase, i32 rounds);
string export_room_keys([ByRef] string passphrase, i32 rounds);
[Throws=KeyImportError]
KeysImportResult import_keys(
KeysImportResult import_room_keys(
[ByRef] string keys,
[ByRef] string passphrase,
ProgressListener progress_listener
);
[Throws=KeyImportError]
KeysImportResult import_decrypted_keys(
KeysImportResult import_decrypted_room_keys(
[ByRef] string keys,
ProgressListener progress_listener
);
+18 -12
View File
@@ -1,18 +1,18 @@
[package]
authors = ["Ivan Enderlin <ivane@element.io>"]
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"
name = "matrix-sdk-crypto-js"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
version = "0.5.0"
version = "0.1.0-alpha.0"
publish = false
[package.metadata.docs.rs]
features = ["docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[package.metadata.wasm-pack.profile.release]
@@ -22,18 +22,24 @@ wasm-opt = ['-Oz']
crate-type = ["cdylib"]
[features]
default = []
qrcode = ["matrix-sdk-crypto/qrcode"]
docsrs = []
default = ["tracing", "qrcode"]
qrcode = ["matrix-sdk-crypto/qrcode", "dep:matrix-sdk-qrcode"]
tracing = []
[dependencies]
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] }
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb", features = ["experimental-nodejs"] }
matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true }
ruma = { version = "0.7.0", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { version = "0.3.0", features = ["js"] }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = "0.4.30"
js-sys = "0.3.49"
console_error_panic_hook = "0.1.7"
serde_json = "1.0.79"
http = "0.2.6"
anyhow = "1.0"
anyhow = "1.0.58"
tracing = { version = "0.1.35", default-features = false, features = ["attributes"] }
tracing-subscriber = { version = "0.3.14", default-features = false, features = ["registry", "std"] }
zeroize = "1.3.0"
+5 -1
View File
@@ -37,7 +37,11 @@ TBD
## Documentation
To generate the documentation, please run the following command:
[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
$ npm run doc
+61
View File
@@ -0,0 +1,61 @@
# configuration file for git-cliff (0.1.0)
[changelog]
# changelog header
header = """
# Matrix SDK Crypto JavaScript Changelog\n
All notable changes to this project will be documented in this file.\n
"""
# template for the changelog body
# https://tera.netlify.app/docs/#introduction
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [unreleased]
{% endif %}\
{% for group, commits in commits | filter(attribute="scope", value="crypto-js") | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
"""
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = true
# filter out the commits that are not conventional
filter_unconventional = true
# regex for preprocessing the commit messages
commit_preprocessors = [
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](https://github.com/matrix-org/matrix-rust-sdk/issues/${2}))"},
]
# regex for parsing and grouping commits
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^test", group = "Testing"},
{ message = "^doc", group = "Documentation"},
{ message = "^refactor", group = "Refactoring"},
{ message = "^ci", group = "Continuous Integration"},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
filter_commits = false
# glob pattern for matching git tags
tag_pattern = "v[0-9]*"
# regex for skipping tags
skip_tags = ""
# regex for ignoring tags
ignore_tags = ""
# sort the tags chronologically
date_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "oldest"
+12 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@matrix-org/matrix-sdk-crypto-js",
"version": "0.5.0",
"version": "0.1.0-alpha.0",
"homepage": "https://github.com/matrix-org/matrix-rust-sdk",
"description": "Matrix encryption library, for JavaScript",
"license": "Apache-2.0",
@@ -26,16 +26,23 @@
"pkg/matrix_sdk_crypto.d.ts"
],
"devDependencies": {
"wasm-pack": "^0.10.2",
"cross-env": "^7.0.3",
"fake-indexeddb": "^4.0",
"jest": "^28.1.0",
"typedoc": "^0.22.17"
"typedoc": "^0.22.17",
"wasm-pack": "^0.10.2",
"yargs-parser": "~21.0.1"
},
"engines": {
"node": ">= 10"
},
"scripts": {
"build": "RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --out-name matrix_sdk_crypto --out-dir ./pkg",
"build": "cross-env RUSTFLAGS='-C opt-level=z' wasm-pack build --release --target nodejs --scope matrix-org --out-dir ./pkg",
"test": "jest --verbose",
"doc": "typedoc --tsconfig ."
"doc": "typedoc --tsconfig .",
"prepack": "npm run build && npm run test",
"pack": "wasm-pack pack",
"prepublish": "npm run pack",
"publish": "wasm-pack publish"
}
}
@@ -0,0 +1,124 @@
//! 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 media_encryption_info = match attachment.media_encryption_info.take() {
Some(media_encryption_info) => media_encryption_info,
None => {
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()
}
}
+249
View File
@@ -0,0 +1,249 @@
//! Types for a `Device`.
use js_sys::{Array, Map, Promise};
use wasm_bindgen::prelude::*;
use crate::{
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.
#[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()
}
/// 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 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()
}
}
/// 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()
}
}
@@ -29,6 +29,11 @@ pub struct EncryptionSettings {
/// 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 {
@@ -40,6 +45,7 @@ impl Default for EncryptionSettings {
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,
}
}
}
@@ -55,11 +61,14 @@ impl EncryptionSettings {
impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
fn from(value: &EncryptionSettings) -> Self {
let algorithm = value.algorithm.clone().into();
Self {
algorithm: value.algorithm.clone().into(),
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,
}
}
}
@@ -76,7 +85,7 @@ pub enum EncryptionAlgorithm {
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
@@ -87,9 +96,9 @@ impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
}
}
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
use ruma::EventEncryptionAlgorithm::*;
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,
+161 -15
View File
@@ -3,6 +3,8 @@
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
@@ -12,11 +14,7 @@ pub struct UserId {
pub(crate) inner: ruma::OwnedUserId,
}
impl From<ruma::OwnedUserId> for UserId {
fn from(inner: ruma::OwnedUserId) -> Self {
Self { inner }
}
}
impl_from_to_inner!(ruma::OwnedUserId => UserId);
#[wasm_bindgen]
impl UserId {
@@ -66,11 +64,7 @@ pub struct DeviceId {
pub(crate) inner: ruma::OwnedDeviceId,
}
impl From<ruma::OwnedDeviceId> for DeviceId {
fn from(inner: ruma::OwnedDeviceId) -> Self {
Self { inner }
}
}
impl_from_to_inner!(ruma::OwnedDeviceId => DeviceId);
#[wasm_bindgen]
impl DeviceId {
@@ -88,6 +82,122 @@ impl DeviceId {
}
}
/// 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
@@ -97,11 +207,7 @@ pub struct RoomId {
pub(crate) inner: ruma::OwnedRoomId,
}
impl From<ruma::OwnedRoomId> for RoomId {
fn from(inner: ruma::OwnedRoomId) -> Self {
Self { inner }
}
}
impl_from_to_inner!(ruma::OwnedRoomId => RoomId);
#[wasm_bindgen]
impl RoomId {
@@ -173,3 +279,43 @@ impl ServerName {
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()
}
}
@@ -0,0 +1,172 @@
//! 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?) })
}
}
/// 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)?)
}))
}
}
+40
View File
@@ -0,0 +1,40 @@
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()
}
+19 -27
View File
@@ -17,42 +17,34 @@
#![warn(missing_docs, missing_debug_implementations)]
#![allow(clippy::drop_non_drop)] // triggered by wasm_bindgen code
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::{Object, Reflect};
use wasm_bindgen::{convert::RefFromWasmAbi, prelude::*};
use wasm_bindgen::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.
/// Run some stuff when the Wasm module is instantiated.
///
/// The returned value is likely to be a `wasm_bindgen::__ref::Ref<T>`.
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 `{}` instance, received `{}` instead",
classname, constructor_name,
)))
}
/// Right now, it does the following:
///
/// * Redirect Rust panics to JavaScript console.
#[wasm_bindgen(start)]
pub fn start() {
console_error_panic_hook::set_once();
}
+356 -71
View File
@@ -2,21 +2,20 @@
use std::collections::BTreeMap;
use js_sys::{Array, Map, Promise, Set};
use ruma::{
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
OwnedTransactionId, UInt,
};
use serde_json::Value as JsonValue;
use js_sys::{Array, Function, Map, Promise, Set};
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{json, Value as JsonValue};
use wasm_bindgen::prelude::*;
use crate::{
downcast, encryption,
device, encryption,
future::future_to_promise,
identifiers, requests,
identifiers, identities,
js::downcast,
olm, requests,
requests::OutgoingRequest,
responses::{self, response_from_string},
sync_events,
store, sync_events, types, verification, vodozemac,
};
/// State machine implementation of the Olm/Megolm encryption protocol
@@ -37,16 +36,74 @@ impl OlmMachine {
/// `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` and `store_passphrase` are both optional, but
/// must be both set to have an effect. If they are both set, the
/// state of the machine will persist in a database named
/// `store_name` where its content is encrypted by the passphrase
/// given by `store_passphrase`. If they are not both set, the
/// created machine will keep the encryption keys only in memory,
/// and once the object is dropped, the keys will be lost.
#[wasm_bindgen(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new(user_id: &identifiers::UserId, device_id: &identifiers::DeviceId) -> Promise {
pub fn new(
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) {
// 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
// practise, it doesn't impact this crate because it's
// always compiled for `wasm32`.
#[cfg(target_arch = "wasm32")]
(Some(store_name), Some(mut store_passphrase)) => {
use std::sync::Arc;
use zeroize::Zeroize;
let store = Some(
matrix_sdk_indexeddb::IndexeddbCryptoStore::open_with_passphrase(
&store_name,
&store_passphrase,
)
.await
.map(Arc::new)?,
);
store_passphrase.zeroize();
store
}
(Some(_), None) => return Err(anyhow::Error::msg("The `store_name` has been set, and so, it expects a `store_passphrase`, which is not set; please provide one")),
(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,
};
Ok(OlmMachine {
inner: matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
.await,
inner: match store {
Some(store) => {
matrix_sdk_crypto::OlmMachine::with_store(
user_id.as_ref(),
device_id.as_ref(),
store,
)
.await?
}
None => {
matrix_sdk_crypto::OlmMachine::new(user_id.as_ref(), device_id.as_ref())
.await
}
},
})
})
}
@@ -65,7 +122,7 @@ impl OlmMachine {
/// Get the public parts of our Olm identity keys.
#[wasm_bindgen(getter, js_name = "identityKeys")]
pub fn identity_keys(&self) -> IdentityKeys {
pub fn identity_keys(&self) -> vodozemac::IdentityKeys {
self.inner.identity_keys().into()
}
@@ -274,7 +331,7 @@ impl OlmMachine {
event: &str,
room_id: &identifiers::RoomId,
) -> Result<Promise, JsError> {
let event: OriginalSyncRoomEncryptedEvent = serde_json::from_str(event)?;
let event: Raw<_> = serde_json::from_str(event)?;
let room_id = room_id.inner.clone();
let me = self.inner.clone();
@@ -285,6 +342,98 @@ impl OlmMachine {
}))
}
/// 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 seed for the ed25519 keys as a
/// unpadded base64 encoded string.
///
/// This method returns None if we dont have any private cross
/// signing keys.
#[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 export needs to contain the seed for the ed25519 keys as
/// an unpadded base64 encoded string.
#[wasm_bindgen(js_name = "importCrossSigningKeys")]
pub fn import_cross_signing_keys(&self, export: store::CrossSigningKeyExport) -> Promise {
let me = self.inner.clone();
let export = export.inner;
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.
#[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.
#[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.
///
@@ -377,70 +526,206 @@ impl OlmMachine {
}
}))
}
}
/// An Ed25519 public key, used to verify digital signatures.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Ed25519PublicKey {
inner: vodozemac::Ed25519PublicKey,
}
/// 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();
#[wasm_bindgen]
impl Ed25519PublicKey {
/// The number of bytes an Ed25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Ed25519PublicKey::LENGTH
let me = self.inner.clone();
future_to_promise::<_, device::UserDevices>(async move {
Ok(me.get_user_devices(&user_id, None).await.map(Into::into)?)
})
}
/// 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()
}
}
/// 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();
/// A Curve25519 public key.
#[wasm_bindgen]
#[derive(Debug, Clone)]
pub struct Curve25519PublicKey {
inner: vodozemac::Curve25519PublicKey,
}
let me = self.inner.clone();
#[wasm_bindgen]
impl Curve25519PublicKey {
/// The number of bytes a Curve25519 public key has.
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
vodozemac::Curve25519PublicKey::LENGTH
future_to_promise::<_, Option<device::Device>>(async move {
Ok(me.get_device(&user_id, &device_id, None).await?.map(Into::into))
})
}
/// 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()
}
}
/// 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 },
}
/// 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 an unencrypted verification event.
///
/// This method can be used to pass verification events that are
/// happening in unencrypted rooms to the `OlmMachine`.
///
/// Note: This does not need to be called for encrypted events
/// since those will get passed to the `OlmMachine` during
/// decryption.
#[wasm_bindgen(js_name = "receiveUnencryptedVerificationEvent")]
pub fn receive_unencrypted_verification_event(&self, event: &str) -> Result<Promise, JsError> {
let event: ruma::events::AnyMessageLikeEvent = serde_json::from_str(event)?;
let me = self.inner.clone();
Ok(future_to_promise(async move {
Ok(me
.receive_unencrypted_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,
)?)?)
}
}
@@ -0,0 +1,33 @@
/// 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 }
}
}
};
}
+72
View File
@@ -0,0 +1,72 @@
//! Olm types.
use wasm_bindgen::prelude::*;
use crate::{identifiers, impl_from_to_inner};
/// 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()
}
/// 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()
}
}
+84 -37
View File
@@ -3,18 +3,23 @@
use js_sys::JsString;
use matrix_sdk_crypto::{
requests::{
KeysBackupRequest as RumaKeysBackupRequest, KeysQueryRequest as RumaKeysQueryRequest,
RoomMessageRequest as RumaRoomMessageRequest, ToDeviceRequest as RumaToDeviceRequest,
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 RumaKeysClaimRequest,
upload_keys::v3::Request as RumaKeysUploadRequest,
upload_signatures::v3::Request as RumaSignatureUploadRequest,
claim_keys::v3::Request as OriginalKeysClaimRequest,
upload_keys::v3::Request as OriginalKeysUploadRequest,
upload_signatures::v3::Request as OriginalSignatureUploadRequest,
};
use wasm_bindgen::prelude::*;
/** Outgoing Requests * */
/// Data for a request to the `/keys/upload` API endpoint
/// ([specification]).
///
@@ -26,7 +31,7 @@ use wasm_bindgen::prelude::*;
pub struct KeysUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -42,7 +47,7 @@ impl KeysUploadRequest {
/// Create a new `KeysUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysUploadRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -63,7 +68,7 @@ impl KeysUploadRequest {
pub struct KeysQueryRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -79,7 +84,7 @@ impl KeysQueryRequest {
/// Create a new `KeysQueryRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysQueryRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -101,7 +106,7 @@ impl KeysQueryRequest {
pub struct KeysClaimRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -117,7 +122,7 @@ impl KeysClaimRequest {
/// Create a new `KeysClaimRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> KeysClaimRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -138,7 +143,7 @@ impl KeysClaimRequest {
pub struct ToDeviceRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -154,7 +159,7 @@ impl ToDeviceRequest {
/// Create a new `ToDeviceRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> ToDeviceRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -175,7 +180,7 @@ impl ToDeviceRequest {
pub struct SignatureUploadRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -191,7 +196,7 @@ impl SignatureUploadRequest {
/// Create a new `SignatureUploadRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> SignatureUploadRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -210,7 +215,7 @@ impl SignatureUploadRequest {
pub struct RoomMessageRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -226,7 +231,7 @@ impl RoomMessageRequest {
/// Create a new `RoomMessageRequest`.
#[wasm_bindgen(constructor)]
pub fn new(id: JsString, body: JsString) -> RoomMessageRequest {
Self { id, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -245,7 +250,7 @@ impl RoomMessageRequest {
pub struct KeysBackupRequest {
/// The request ID.
#[wasm_bindgen(readonly)]
pub id: JsString,
pub id: Option<JsString>,
/// A JSON-encoded object of form:
///
@@ -256,12 +261,33 @@ pub struct KeysBackupRequest {
pub body: JsString,
}
/** 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 object of form:
///
/// ```json
/// {"master_key": …, "self_signing_key": …, "user_signing_key": …}
/// ```
#[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, body }
Self { id: Some(id), body }
}
/// Get its request type.
@@ -272,35 +298,56 @@ impl KeysBackupRequest {
}
macro_rules! request {
($request:ident from $ruma_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
impl TryFrom<(String, &$ruma_request)> for $request {
type Error = serde_json::Error;
fn try_from(
(request_id, request): (String, &$ruma_request),
) -> Result<Self, Self::Error> {
($destination_request:ident from $source_request:ident maps fields $( $field:ident ),+ $(,)? ) => {
impl $destination_request {
pub(crate) fn to_json(request: &$source_request) -> Result<String, serde_json::Error> {
let mut map = serde_json::Map::new();
$(
map.insert(stringify!($field).to_owned(), serde_json::to_value(&request.$field).unwrap());
)+
let value = serde_json::Value::Object(map);
let object = serde_json::Value::Object(map);
Ok($request {
id: request_id.into(),
body: serde_json::to_string(&value)?.into(),
serde_json::to_string(&object)
}
}
impl TryFrom<&$source_request> for $destination_request {
type Error = serde_json::Error;
fn try_from(request: &$source_request) -> Result<Self, Self::Error> {
Ok($destination_request {
id: None,
body: Self::to_json(request)?.into(),
})
}
}
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> {
Ok($destination_request {
id: Some(request_id.into()),
body: Self::to_json(request)?.into(),
})
}
}
};
}
request!(KeysUploadRequest from RumaKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from RumaKeysQueryRequest maps fields timeout, device_keys, token);
request!(KeysClaimRequest from RumaKeysClaimRequest maps fields timeout, one_time_keys);
request!(ToDeviceRequest from RumaToDeviceRequest maps fields event_type, txn_id, messages);
request!(SignatureUploadRequest from RumaSignatureUploadRequest maps fields signed_keys);
request!(RoomMessageRequest from RumaRoomMessageRequest maps fields room_id, txn_id, content);
request!(KeysBackupRequest from RumaKeysBackupRequest maps fields rooms);
// Outgoing Requests
request!(KeysUploadRequest from OriginalKeysUploadRequest maps fields device_keys, one_time_keys, fallback_keys);
request!(KeysQueryRequest from OriginalKeysQueryRequest maps fields timeout, device_keys, token);
request!(KeysClaimRequest from OriginalKeysClaimRequest maps fields timeout, one_time_keys);
request!(ToDeviceRequest from OriginalToDeviceRequest maps fields event_type, txn_id, messages);
request!(SignatureUploadRequest from OriginalSignatureUploadRequest maps fields signed_keys);
request!(RoomMessageRequest from OriginalRoomMessageRequest maps fields room_id, txn_id, content);
request!(KeysBackupRequest from OriginalKeysBackupRequest maps fields rooms);
// Other Requests
request!(SigningKeysUploadRequest from OriginalUploadSigningKeysRequest maps fields 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
@@ -182,12 +182,8 @@ impl DecryptedRoomEvent {
/// 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) -> Option<Array> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
forwarding_curve25519_key_chain.iter().map(JsValue::from).collect()
}
})
pub fn forwarding_curve25519_key_chain(&self) -> Array {
Array::new()
}
/// The verification state of the device that sent us the event,
@@ -200,8 +196,8 @@ impl DecryptedRoomEvent {
}
}
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
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,
@@ -0,0 +1,36 @@
//! Store types.
use wasm_bindgen::prelude::*;
use crate::impl_from_to_inner;
/// 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()
}
}
@@ -3,7 +3,7 @@
use js_sys::Array;
use wasm_bindgen::prelude::*;
use crate::{downcast, identifiers};
use crate::{identifiers, js::downcast};
/// Information on E2E device updates.
#[wasm_bindgen]
@@ -0,0 +1,290 @@
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 = "trace")]
fn log_trace(message: String);
#[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_trace(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
@@ -0,0 +1,156 @@
//! 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
@@ -0,0 +1,193 @@
//! 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,
}
@@ -0,0 +1,77 @@
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);
});
});
@@ -0,0 +1,900 @@
const {
OlmMachine,
UserId,
DeviceId,
DeviceKeyId,
RoomId,
Device,
LocalTrust,
UserDevices,
DeviceKey,
DeviceKeyName,
DeviceKeyAlgorithmName,
Ed25519PublicKey,
Curve25519PublicKey,
Signatures,
VerificationMethod,
VerificationRequest,
ToDeviceRequest,
DeviceLists,
KeysUploadRequest,
RequestType,
KeysQueryRequest,
Sas,
Emoji,
SigningKeysUploadRequest,
SignatureUploadRequest,
Qr,
QrCode,
QrCodeScan,
} = require('../pkg/matrix_sdk_crypto_js');
const { zip, addMachineToMachine } = require('./helper');
describe('LocalTrust', () => {
test('has the correct variant values', () => {
expect(LocalTrust.Verified).toStrictEqual(0);
expect(LocalTrust.BlackListed).toStrictEqual(1);
expect(LocalTrust.Ignored).toStrictEqual(2);
expect(LocalTrust.Unset).toStrictEqual(3);
});
});
describe('DeviceKeyName', () => {
test('has the correct variant values', () => {
expect(DeviceKeyName.Curve25519).toStrictEqual(0);
expect(DeviceKeyName.Ed25519).toStrictEqual(1);
expect(DeviceKeyName.Unknown).toStrictEqual(2);
});
});
describe(OlmMachine.name, () => {
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 new OlmMachine(new_user || user, new_device || device);
}
test('can read user devices', async () => {
const m = await machine();
const userDevices = await m.getUserDevices(user);
expect(userDevices).toBeInstanceOf(UserDevices);
expect(userDevices.get(device)).toBeInstanceOf(Device);
expect(userDevices.isAnyVerified()).toStrictEqual(false);
expect(userDevices.keys().map(device_id => device_id.toString())).toStrictEqual([device.toString()]);
expect(userDevices.devices().map(device => device.deviceId.toString())).toStrictEqual([device.toString()]);
});
test('can read a user device', async () => {
const m = await machine();
const dev = await m.getDevice(user, device);
expect(dev).toBeInstanceOf(Device);
expect(dev.isVerified()).toStrictEqual(false);
expect(dev.isCrossSigningTrusted()).toStrictEqual(false);
expect(dev.localTrustState).toStrictEqual(LocalTrust.Unset);
expect(dev.isLocallyTrusted()).toStrictEqual(false);
expect(await dev.setLocalTrust(LocalTrust.Verified)).toBeNull();
expect(dev.localTrustState).toStrictEqual(LocalTrust.Verified);
expect(dev.isLocallyTrusted()).toStrictEqual(true);
expect(dev.userId.toString()).toStrictEqual(user.toString());
expect(dev.deviceId.toString()).toStrictEqual(device.toString());
expect(dev.deviceName).toBeUndefined();
const deviceKey = dev.getKey(DeviceKeyAlgorithmName.Ed25519);
expect(deviceKey).toBeInstanceOf(DeviceKey);
expect(deviceKey.name).toStrictEqual(DeviceKeyName.Ed25519);
expect(deviceKey.curve25519).toBeUndefined();
expect(deviceKey.ed25519).toBeInstanceOf(Ed25519PublicKey);
expect(deviceKey.unknown).toBeUndefined();
expect(deviceKey.toBase64()).toMatch(/^[A-Za-z0-9\+/]+$/);
expect(dev.curve25519Key).toBeInstanceOf(Curve25519PublicKey);
expect(dev.ed25519Key).toBeInstanceOf(Ed25519PublicKey);
for (const [deviceKeyId, deviceKey] of dev.keys) {
expect(deviceKeyId).toBeInstanceOf(DeviceKeyId);
expect(deviceKey).toBeInstanceOf(DeviceKey);
}
expect(dev.signatures).toBeInstanceOf(Signatures);
expect(dev.isBlacklisted()).toStrictEqual(false);
expect(dev.isDeleted()).toStrictEqual(false);
});
});
describe('Key Verification', () => {
const userId1 = new UserId('@alice:example.org');
const deviceId1 = new DeviceId('alice_device');
const userId2 = new UserId('@bob:example.org');
const deviceId2 = new DeviceId('bob_device');
function machine(new_user, new_device) {
return new OlmMachine(new_user || userId1, new_device || deviceId1);
}
describe('SAS', () => {
// First Olm machine.
let m1;
// Second Olm machine.
let m2;
beforeAll(async () => {
m1 = await machine(userId1, deviceId1);
m2 = await machine(userId2, deviceId2);
});
// Verification request for `m1`.
let verificationRequest1;
// The flow ID.
let flowId;
test('can request verification (`m.key.verification.request`)', async () => {
// Make `m1` and `m2` be aware of each other.
{
await addMachineToMachine(m2, m1);
await addMachineToMachine(m1, m2);
}
// Pick the device we want to start the verification with.
const device2 = await m1.getDevice(userId2, deviceId2);
expect(device2).toBeInstanceOf(Device);
let outgoingVerificationRequest;
// Request a verification from `m1` to `device2`.
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification();
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
expect(verificationRequest1.otherDeviceId).toBeUndefined();
expect(verificationRequest1.roomId).toBeUndefined();
expect(verificationRequest1.cancelInfo).toBeUndefined();
expect(verificationRequest1.isPassive()).toStrictEqual(false);
expect(verificationRequest1.isReady()).toStrictEqual(false);
expect(verificationRequest1.timedOut()).toStrictEqual(false);
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest1.weStarted()).toStrictEqual(true);
expect(verificationRequest1.isDone()).toStrictEqual(false);
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
flowId = verificationRequest1.flowId;
});
// Verification request for `m2`.
let verificationRequest2;
test('can fetch received request verification', async () => {
// Oh, a new verification request.
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
expect(verificationRequest2.roomId).toBeUndefined();
expect(verificationRequest2.cancelInfo).toBeUndefined();
expect(verificationRequest2.isPassive()).toStrictEqual(false);
expect(verificationRequest2.isReady()).toStrictEqual(false);
expect(verificationRequest2.timedOut()).toStrictEqual(false);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
expect(verificationRequest2.flowId).toStrictEqual(flowId);
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest2.weStarted()).toStrictEqual(false);
expect(verificationRequest2.isDone()).toStrictEqual(false);
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
const verificationRequests = m2.getVerificationRequests(userId1);
expect(verificationRequests).toHaveLength(1);
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
});
test('can accept a verification request (`m.key.verification.ready`)', async () => {
// Accept the verification request.
let outgoingVerificationRequest = verificationRequest2.accept();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
// The request verification is ready.
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send the verification ready to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('verification requests are synchronized and automatically updated', () => {
expect(verificationRequest1.isReady()).toStrictEqual(true);
expect(verificationRequest2.isReady()).toStrictEqual(true);
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.SasV1, VerificationMethod.ReciprocateV1]));
});
// SAS verification for the second machine.
let sas2;
test('can start a SAS verification (`m.key.verification.start`)', async () => {
// Let's start a SAS verification, from `m2` for example.
[sas2, outgoingVerificationRequest] = await verificationRequest2.startSas();
expect(sas2).toBeInstanceOf(Sas);
expect(sas2.userId.toString()).toStrictEqual(userId2.toString());
expect(sas2.deviceId.toString()).toStrictEqual(deviceId2.toString());
expect(sas2.otherUserId.toString()).toStrictEqual(userId1.toString());
expect(sas2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
expect(sas2.flowId).toStrictEqual(flowId);
expect(sas2.roomId).toBeUndefined();
expect(sas2.supportsEmoji()).toStrictEqual(false);
expect(sas2.startedFromRequest()).toStrictEqual(true);
expect(sas2.isSelfVerification()).toStrictEqual(false);
expect(sas2.haveWeConfirmed()).toStrictEqual(false);
expect(sas2.hasBeenAccepted()).toStrictEqual(false);
expect(sas2.cancelInfo()).toBeUndefined();
expect(sas2.weStarted()).toStrictEqual(false);
expect(sas2.timedOut()).toStrictEqual(false);
expect(sas2.canBePresented()).toStrictEqual(false);
expect(sas2.isDone()).toStrictEqual(false);
expect(sas2.isCancelled()).toStrictEqual(false);
expect(sas2.emoji()).toBeUndefined();
expect(sas2.emojiIndex()).toBeUndefined();
expect(sas2.decimals()).toBeUndefined();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send the SAS start to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
// SAS verification for the second machine.
let sas1;
test('can fetch and accept an ongoing SAS verification (`m.key.verification.accept`)', async () => {
// Let's fetch the ongoing SAS verification.
sas1 = await m1.getVerification(userId2, flowId);
expect(sas1).toBeInstanceOf(Sas);
expect(sas1.userId.toString()).toStrictEqual(userId1.toString());
expect(sas1.deviceId.toString()).toStrictEqual(deviceId1.toString());
expect(sas1.otherUserId.toString()).toStrictEqual(userId2.toString());
expect(sas1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
expect(sas1.flowId).toStrictEqual(flowId);
expect(sas1.roomId).toBeUndefined();
expect(sas1.startedFromRequest()).toStrictEqual(true);
expect(sas1.isSelfVerification()).toStrictEqual(false);
expect(sas1.haveWeConfirmed()).toStrictEqual(false);
expect(sas1.hasBeenAccepted()).toStrictEqual(false);
expect(sas1.cancelInfo()).toBeUndefined();
expect(sas1.weStarted()).toStrictEqual(true);
expect(sas1.timedOut()).toStrictEqual(false);
expect(sas1.canBePresented()).toStrictEqual(false);
expect(sas1.isDone()).toStrictEqual(false);
expect(sas1.isCancelled()).toStrictEqual(false);
expect(sas1.emoji()).toBeUndefined();
expect(sas1.emojiIndex()).toBeUndefined();
expect(sas1.decimals()).toBeUndefined();
// Let's accept thet SAS start request.
let outgoingVerificationRequest = sas1.accept();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.accept');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
// Let's send the SAS accept to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('emojis are supported by both sides', () => {
expect(sas1.supportsEmoji()).toStrictEqual(true);
expect(sas2.supportsEmoji()).toStrictEqual(true);
});
test('one side sends verification key (`m.key.verification.key`)', async () => {
// Let's send the verification keys from `m2` to `m1`.
const outgoingRequests = await m2.outgoingRequests();
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send te SAS key to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m2.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
});
test('other side sends back verification key (`m.key.verification.key`)', async () => {
// Let's send the verification keys from `m1` to `m2`.
const outgoingRequests = await m1.outgoingRequests();
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.key');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
// Let's send te SAS key to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
});
test('emojis match from both sides', () => {
const emojis1 = sas1.emoji();
const emojiIndexes1 = sas1.emojiIndex();
const emojis2 = sas2.emoji();
const emojiIndexes2 = sas2.emojiIndex();
expect(emojis1).toHaveLength(7);
expect(emojiIndexes1).toHaveLength(emojis1.length);
expect(emojis2).toHaveLength(emojis1.length);
expect(emojiIndexes2).toHaveLength(emojis1.length);
const isEmoji = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/;
for (const [emoji1, emojiIndex1, emoji2, emojiIndex2] of zip(emojis1, emojiIndexes1, emojis2, emojiIndexes2)) {
expect(emoji1).toBeInstanceOf(Emoji);
expect(emoji1.symbol).toMatch(isEmoji);
expect(emoji1.description).toBeTruthy();
expect(emojiIndex1).toBeGreaterThanOrEqual(0);
expect(emojiIndex1).toBeLessThanOrEqual(63);
expect(emoji2).toBeInstanceOf(Emoji);
expect(emoji2.symbol).toStrictEqual(emoji1.symbol);
expect(emoji2.description).toStrictEqual(emoji1.description);
expect(emojiIndex2).toStrictEqual(emojiIndex1);
}
});
test('decimals match from both sides', () => {
const decimals1 = sas1.decimals();
const decimals2 = sas2.decimals();
expect(decimals1).toHaveLength(3);
expect(decimals2).toHaveLength(decimals1.length);
const isDecimal = /^[0-9]{4}$/;
for (const [decimal1, decimal2] of zip(decimals1, decimals2)) {
expect(decimal1.toString()).toMatch(isDecimal);
expect(decimal2).toStrictEqual(decimal1);
}
});
test('can confirm keys match (`m.key.verification.mac`)', async () => {
// `m1` confirms.
const [outgoingVerificationRequests, signatureUploadRequest] = await sas1.confirm();
expect(signatureUploadRequest).toBeUndefined();
expect(outgoingVerificationRequests).toHaveLength(1);
let outgoingVerificationRequest = outgoingVerificationRequests[0];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
// Let's send te SAS confirmation to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm back keys match (`m.key.verification.done`)', async () => {
// `m2` confirms.
const [outgoingVerificationRequests, signatureUploadRequest] = await sas2.confirm();
expect(signatureUploadRequest).toBeUndefined();
expect(outgoingVerificationRequests).toHaveLength(2);
// `.mac`
{
let outgoingVerificationRequest = outgoingVerificationRequests[0];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.mac');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send te SAS confirmation to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
}
// `.done`
{
let outgoingVerificationRequest = outgoingVerificationRequests[1];
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send te SAS done to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
}
});
test('can send final done (`m.key.verification.done`)', async () => {
const outgoingRequests = await m1.outgoingRequests();
expect(outgoingRequests).toHaveLength(4);
let toDeviceRequest = outgoingRequests.find((request) => request.type == RequestType.ToDevice);
expect(toDeviceRequest).toBeInstanceOf(ToDeviceRequest);
const toDeviceRequestId = toDeviceRequest.id;
const toDeviceRequestType = toDeviceRequest.type;
toDeviceRequest = JSON.parse(toDeviceRequest.body);
expect(toDeviceRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: toDeviceRequest.event_type,
content: toDeviceRequest.messages[userId2.toString()][deviceId2.toString()],
}],
};
// Let's send te SAS key to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
m1.markRequestAsSent(toDeviceRequestId, toDeviceRequestType, '{}');
});
test('can see if verification is done', () => {
expect(verificationRequest1.isDone()).toStrictEqual(true);
expect(verificationRequest2.isDone()).toStrictEqual(true);
expect(sas1.isDone()).toStrictEqual(true);
expect(sas2.isDone()).toStrictEqual(true);
});
});
describe('QR Code', () => {
if (undefined === Qr) {
// qrcode supports is not enabled
console.info('qrcode support is disabled, skip the associated test suite');
return;
}
// First Olm machine.
let m1;
// Second Olm machine.
let m2;
beforeAll(async () => {
m1 = await machine(userId1, deviceId1);
m2 = await machine(userId2, deviceId2);
});
// Verification request for `m1`.
let verificationRequest1;
// The flow ID.
let flowId;
test('can request verification (`m.key.verification.request`)', async () => {
// Make `m1` and `m2` be aware of each other.
{
await addMachineToMachine(m2, m1);
await addMachineToMachine(m1, m2);
}
// Pick the device we want to start the verification with.
const device2 = await m1.getDevice(userId2, deviceId2);
expect(device2).toBeInstanceOf(Device);
let outgoingVerificationRequest;
// Request a verification from `m1` to `device2`.
[verificationRequest1, outgoingVerificationRequest] = await device2.requestVerification([
VerificationMethod.QrCodeScanV1, // by default
VerificationMethod.QrCodeShowV1, // the one we add
]);
expect(verificationRequest1).toBeInstanceOf(VerificationRequest);
expect(verificationRequest1.ownUserId.toString()).toStrictEqual(userId1.toString());
expect(verificationRequest1.otherUserId.toString()).toStrictEqual(userId2.toString());
expect(verificationRequest1.otherDeviceId).toBeUndefined();
expect(verificationRequest1.roomId).toBeUndefined();
expect(verificationRequest1.cancelInfo).toBeUndefined();
expect(verificationRequest1.isPassive()).toStrictEqual(false);
expect(verificationRequest1.isReady()).toStrictEqual(false);
expect(verificationRequest1.timedOut()).toStrictEqual(false);
expect(verificationRequest1.theirSupportedMethods).toBeUndefined();
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeShowV1]));
expect(verificationRequest1.flowId).toMatch(/^[a-f0-9]+$/);
expect(verificationRequest1.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest1.weStarted()).toStrictEqual(true);
expect(verificationRequest1.isDone()).toStrictEqual(false);
expect(verificationRequest1.isCancelled()).toStrictEqual(false);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.request');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
flowId = verificationRequest1.flowId;
});
// Verification request for `m2`.
let verificationRequest2;
test('can fetch received request verification', async () => {
// Oh, a new verification request.
verificationRequest2 = m2.getVerificationRequest(userId1, flowId);
expect(verificationRequest2).toBeInstanceOf(VerificationRequest);
expect(verificationRequest2.ownUserId.toString()).toStrictEqual(userId2.toString());
expect(verificationRequest2.otherUserId.toString()).toStrictEqual(userId1.toString());
expect(verificationRequest2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
expect(verificationRequest2.roomId).toBeUndefined();
expect(verificationRequest2.cancelInfo).toBeUndefined();
expect(verificationRequest2.isPassive()).toStrictEqual(false);
expect(verificationRequest2.isReady()).toStrictEqual(false);
expect(verificationRequest2.timedOut()).toStrictEqual(false);
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.ourSupportedMethods).toBeUndefined();
expect(verificationRequest2.flowId).toStrictEqual(flowId);
expect(verificationRequest2.isSelfVerification()).toStrictEqual(false);
expect(verificationRequest2.weStarted()).toStrictEqual(false);
expect(verificationRequest2.isDone()).toStrictEqual(false);
expect(verificationRequest2.isCancelled()).toStrictEqual(false);
const verificationRequests = m2.getVerificationRequests(userId1);
expect(verificationRequests).toHaveLength(1);
expect(verificationRequests[0].flowId).toStrictEqual(verificationRequest2.flowId); // there are the same
});
test('can accept a verification request with methods (`m.key.verification.ready`)', async () => {
// Accept the verification request.
let outgoingVerificationRequest = verificationRequest2.acceptWithMethods([
VerificationMethod.QrCodeScanV1, // by default
VerificationMethod.QrCodeShowV1, // the one we add
]);
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
// The request verification is ready.
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.ready');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}],
};
// Let's send the verification ready to `m1`.
await m1.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('verification requests are synchronized and automatically updated', () => {
expect(verificationRequest1.isReady()).toStrictEqual(true);
expect(verificationRequest2.isReady()).toStrictEqual(true);
expect(verificationRequest1.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest1.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.theirSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
expect(verificationRequest2.ourSupportedMethods).toEqual(expect.arrayContaining([VerificationMethod.QrCodeScanV1, VerificationMethod.QrCodeShowV1]));
});
// QR verification for the second machine.
let qr2;
test('can generate a QR code', async () => {
qr2 = await verificationRequest2.generateQrCode();
expect(qr2).toBeInstanceOf(Qr);
expect(qr2.hasBeenScanned()).toStrictEqual(false);
expect(qr2.hasBeenConfirmed()).toStrictEqual(false);
expect(qr2.userId.toString()).toStrictEqual(userId2.toString());
expect(qr2.otherUserId.toString()).toStrictEqual(userId1.toString());
expect(qr2.otherDeviceId.toString()).toStrictEqual(deviceId1.toString());
expect(qr2.weStarted()).toStrictEqual(false);
expect(qr2.cancelInfo()).toBeUndefined();
expect(qr2.isDone()).toStrictEqual(false);
expect(qr2.isCancelled()).toStrictEqual(false);
expect(qr2.isSelfVerification()).toStrictEqual(false);
expect(qr2.reciprocated()).toStrictEqual(false);
expect(qr2.flowId).toMatch(/^[a-f0-9]+$/);
expect(qr2.roomId).toBeUndefined();
});
let qrCodeBytes;
test('can read QR code\'s bytes', async () => {
const qrCodeHeader = 'MATRIX';
const qrCodeVersion = '\x02';
qrCodeBytes = qr2.toBytes();
expect(qrCodeBytes).toHaveLength(122);
expect(qrCodeBytes.slice(0, 7)).toStrictEqual([...qrCodeHeader, ...qrCodeVersion].map(char => char.charCodeAt(0)));
});
test('can render QR code', async () => {
const qrCode = qr2.toQrCode();
expect(qrCode).toBeInstanceOf(QrCode);
// Want to get `canvasBuffer` to render the QR code? Install `npm install canvas` and uncomment the following blocks.
//let canvasBuffer;
{
const buffer = qrCode.renderIntoBuffer();
expect(buffer).toBeInstanceOf(Uint8ClampedArray);
// 45px ⨉ 45px
expect(buffer).toHaveLength(45 * 45);
// 0 for a white pixel, 1 for a black pixel.
expect(buffer.every(p => p == 0 || p == 1)).toStrictEqual(true);
/*
const { Canvas } = require('canvas');
const canvas = new Canvas(55, 55);
const context = canvas.getContext('2d');
context.fillStyle = 'white';
context.fillRect(0, 0, canvas.width, canvas.height);
// New image data, filled with black, transparent pixels.
const imageData = context.createImageData(45, 45);
const data = imageData.data;
const [r, g, b, a] = [0, 1, 2, 3];
for (
let dataNth = 0,
bufferNth = 0;
dataNth < data.length && bufferNth < buffer.length;
dataNth += 4,
bufferNth += 1
) {
data[dataNth + a] = 255;
// White pixel
if (buffer[bufferNth] == 0) {
data[dataNth + r] = 255;
data[dataNth + g] = 255;
data[dataNth + b] = 255;
}
}
context.putImageData(imageData, 5, 5);
canvasBuffer = canvas.toBuffer('image/png');
*/
}
// Want to see the QR code? Uncomment the following block.
/*
{
const fs = require('fs/promises');
const path = require('path');
const os = require('os');
const tempDirectory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
const qrCodeFile = path.join(tempDirectory, 'qrcode.png');
console.log(`View the QR code at \`${qrCodeFile}\`.`);
expect(await fs.writeFile(qrCodeFile, canvasBuffer)).toBeUndefined();
}
*/
});
let qr1;
test('can scan a QR code from bytes', async () => {
const scan = QrCodeScan.fromBytes(qrCodeBytes);
expect(scan).toBeInstanceOf(QrCodeScan);
qr1 = await verificationRequest1.scanQrCode(scan);
expect(qr1).toBeInstanceOf(Qr);
expect(qr1.hasBeenScanned()).toStrictEqual(false);
expect(qr1.hasBeenConfirmed()).toStrictEqual(false);
expect(qr1.userId.toString()).toStrictEqual(userId1.toString());
expect(qr1.otherUserId.toString()).toStrictEqual(userId2.toString());
expect(qr1.otherDeviceId.toString()).toStrictEqual(deviceId2.toString());
expect(qr1.weStarted()).toStrictEqual(true);
expect(qr1.cancelInfo()).toBeUndefined();
expect(qr1.isDone()).toStrictEqual(false);
expect(qr1.isCancelled()).toStrictEqual(false);
expect(qr1.isSelfVerification()).toStrictEqual(false);
expect(qr1.reciprocated()).toStrictEqual(true);
expect(qr1.flowId).toMatch(/^[a-f0-9]+$/);
expect(qr1.roomId).toBeUndefined();
});
test('can start a QR verification/reciprocate (`m.key.verification.start`)', async () => {
let outgoingVerificationRequest = qr1.reciprocate();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.start');
const toDeviceEvents = {
events: [{
sender: userId1.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId2.toString()][deviceId2.toString()],
}]
};
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm QR code has been scanned', () => {
expect(qr2.hasBeenScanned()).toStrictEqual(true);
});
test('can confirm scanning (`m.key.verification.done`)', async () => {
let outgoingVerificationRequest = qr2.confirmScanning();
expect(outgoingVerificationRequest).toBeInstanceOf(ToDeviceRequest);
outgoingVerificationRequest = JSON.parse(outgoingVerificationRequest.body);
expect(outgoingVerificationRequest.event_type).toStrictEqual('m.key.verification.done');
const toDeviceEvents = {
events: [{
sender: userId2.toString(),
type: outgoingVerificationRequest.event_type,
content: outgoingVerificationRequest.messages[userId1.toString()][deviceId1.toString()],
}]
};
// Let's send the verification request to `m2`.
await m2.receiveSyncChanges(JSON.stringify(toDeviceEvents), new DeviceLists(), new Map(), new Set());
});
test('can confirm QR code has been confirmed', () => {
expect(qr2.hasBeenConfirmed()).toStrictEqual(true);
});
});
});
describe('VerificationMethod', () => {
test('has the correct variant values', () => {
expect(VerificationMethod.SasV1).toStrictEqual(0);
expect(VerificationMethod.QrCodeScanV1).toStrictEqual(1);
expect(VerificationMethod.QrCodeShowV1).toStrictEqual(2);
expect(VerificationMethod.ReciprocateV1).toStrictEqual(3);
});
});
@@ -1,4 +1,4 @@
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto');
const { EncryptionAlgorithm, EncryptionSettings, HistoryVisibility, VerificationState } = require('../pkg/matrix_sdk_crypto_js');
describe('EncryptionAlgorithm', () => {
test('has the correct variant values', () => {
@@ -1,4 +1,4 @@
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto');
const { HistoryVisibility } = require('../pkg/matrix_sdk_crypto_js');
describe('HistoryVisibility', () => {
test('has the correct variant values', () => {
@@ -0,0 +1,80 @@
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);
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 = body;
}
{
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
let [signingKeysUploadRequest, _] = await machineToAdd.bootstrapCrossSigning(true);
signingKeysUploadRequest = JSON.parse(signingKeysUploadRequest.body);
// 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] = keysUploadRequest.device_keys;
keyQueryResponse.master_keys[userId] = signingKeysUploadRequest.master_key;
keyQueryResponse.self_signing_keys[userId] = signingKeysUploadRequest.self_signing_key;
keyQueryResponse.user_signing_keys[userId] = signingKeysUploadRequest.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,4 +1,13 @@
const { UserId, DeviceId, RoomId, ServerName } = require('../pkg/matrix_sdk_crypto');
const {
DeviceId,
DeviceKeyAlgorithm,
DeviceKeyAlgorithmName,
DeviceKeyId,
EventId,
RoomId,
ServerName,
UserId,
} = require('../pkg/matrix_sdk_crypto_js');
describe(UserId.name, () => {
test('cannot be invalid', () => {
@@ -32,6 +41,52 @@ describe(DeviceId.name, () => {
})
});
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();
@@ -70,3 +125,57 @@ describe(ServerName.name, () => {
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,10 +1,91 @@
const { OlmMachine, UserId, DeviceId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState } = require('../pkg/matrix_sdk_crypto');
const {
CrossSigningStatus,
DecryptedRoomEvent,
DeviceId,
DeviceKeyId,
DeviceLists,
EncryptionSettings,
InboundGroupSession,
KeysClaimRequest,
KeysQueryRequest,
KeysUploadRequest,
MaybeSignature,
OlmMachine,
OwnUserIdentity,
RequestType,
RoomId,
SignatureUploadRequest,
ToDeviceRequest,
UserId,
VerificationRequest,
VerificationState,
} = require('../pkg/matrix_sdk_crypto_js');
const { addMachineToMachine } = require('./helper');
require('fake-indexeddb/auto');
describe(OlmMachine.name, () => {
test('can be instantiated with the async initializer', async () => {
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
});
test('can be instantiated with a store', async () => {
// No databases.
expect(await indexedDB.databases()).toHaveLength(0);
let store_name = 'hello';
let store_passphrase = 'world';
// Creating a new Olm machine.
expect(await new OlmMachine(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();
expect(databases).toHaveLength(2);
expect(databases).toStrictEqual([
{ name: `${store_name}::matrix-sdk-crypto-meta`, version: 1 },
{ name: `${store_name}::matrix-sdk-crypto`, version: 1 },
]);
// Creating a new Olm machine, with the stored state.
expect(await new OlmMachine(new UserId('@foo:bar.org'), new DeviceId('baz'), store_name, store_passphrase)).toBeInstanceOf(OlmMachine);
// Same number of databases.
expect(await indexedDB.databases()).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 new OlmMachine(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 new OlmMachine(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');
@@ -299,7 +380,8 @@ describe(OlmMachine.name, () => {
room,
'm.room.message',
JSON.stringify({
"hello": "world"
"msgtype": "m.text",
"body": "Hello, World!"
}),
));
@@ -328,7 +410,8 @@ describe(OlmMachine.name, () => {
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
const event = JSON.parse(decrypted.event);
expect(event.content.hello).toStrictEqual("world");
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());
@@ -338,4 +421,149 @@ describe(OlmMachine.name, () => {
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
});
});
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 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 m;
let exportedRoomKeys;
test('can export room keys', async () => {
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.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);
};
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),
});
});
});
});
@@ -1,4 +1,4 @@
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto');
const { RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, ToDeviceRequest, SignatureUploadRequest, RoomMessageRequest, KeysBackupRequest } = require('../pkg/matrix_sdk_crypto_js');
describe('RequestType', () => {
test('has the correct variant values', () => {
@@ -12,7 +12,7 @@ describe('RequestType', () => {
});
});
for (const [request, request_type] of [
for (const [request, requestType] of [
[KeysUploadRequest, RequestType.KeysUpload],
[KeysQueryRequest, RequestType.KeysQuery],
[KeysClaimRequest, RequestType.KeysClaim],
@@ -28,7 +28,7 @@ for (const [request, request_type] of [
expect(r).toBeInstanceOf(request);
expect(r.id).toStrictEqual('foo');
expect(r.body).toStrictEqual('{"bar": "baz"}');
expect(r.type).toStrictEqual(request_type);
expect(r.type).toStrictEqual(requestType);
});
})
@@ -1,4 +1,4 @@
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto');
const { DeviceLists, UserId } = require('../pkg/matrix_sdk_crypto_js');
describe(DeviceLists.name, () => {
test('can be empty', () => {
@@ -0,0 +1,84 @@
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 new OlmMachine(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 -1
View File
@@ -3,7 +3,7 @@
"strict": true
},
"typedocOptions": {
"entryPoints": ["pkg/matrix_sdk_crypto.d.ts"],
"entryPoints": ["pkg/matrix_sdk_crypto_js.d.ts"],
"out": "docs",
"readme": "README.md",
}
@@ -1,6 +1,10 @@
# Matrix-Rust-SDK Node.js Bindings
## 0.1.0-beta.0 - 2022-07-21
## 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
+12 -11
View File
@@ -9,10 +9,9 @@ name = "matrix-sdk-crypto-nodejs"
readme = "README.md"
repository = "https://github.com/matrix-org/matrix-rust-sdk"
rust-version = "1.60"
version = "0.5.0"
version = "0.6.0"
[package.metadata.docs.rs]
features = ["docsrs"]
rustdoc-args = ["--cfg", "docsrs"]
[lib]
@@ -21,21 +20,23 @@ crate-type = ["cdylib"]
[features]
default = []
qrcode = ["matrix-sdk-crypto/qrcode"]
docsrs = []
tracing = ["tracing-subscriber"]
tracing = ["dep:tracing-subscriber"]
[dependencies]
matrix-sdk-crypto = { version = "0.5.0", path = "../../crates/matrix-sdk-crypto" }
matrix-sdk-common = { version = "0.5.0", path = "../../crates/matrix-sdk-common" }
matrix-sdk-sled = { version = "0.1.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
ruma = { git = "https://github.com/ruma/ruma", rev = "96155915f", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" }
napi = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t", default-features = false, features = ["napi6", "tokio_rt"] }
napi-derive = { git = "https://github.com/Hywan/napi-rs", branch = "fix-napi-strict-on-t-and-ref-t" }
matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] }
matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] }
matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] }
ruma = { version = "0.7.0", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] }
napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] }
napi-derive = "2.9.1"
serde_json = "1.0.79"
http = "0.2.6"
zeroize = "1.3.0"
tracing-subscriber = { version = "0.3", default-features = false, features = ["tracing-log", "time", "smallvec", "fmt", "env-filter"], optional = true }
[dependencies.vodozemac]
version = "0.3.0"
features = ["js"]
[build-dependencies]
napi-build = "2.0.0"
+6 -2
View File
@@ -13,7 +13,7 @@ Encryption](https://en.wikipedia.org/wiki/End-to-end_encryption)) for
Just add the latest release to your `package.json`:
```sh
$ npm install --save matrix-sdk-crypto
$ 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:
@@ -186,7 +186,11 @@ to learn more about the `RUST_LOG`/`MATRIX_LOG` environment variable.
## Documentation
To generate the documentation, please run the following command:
[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
+4 -2
View File
@@ -40,9 +40,11 @@ commit_preprocessors = [
commit_parsers = [
{ message = "^feat", group = "Features"},
{ message = "^fix", group = "Bug Fixes"},
{ message = "^doc", group = "Documentation"},
{ message = "^perf", group = "Performance"},
{ message = "^test", group = "Testing"},
{ message = "^doc", group = "Documentation"},
{ message = "^refactor", group = "Refactoring"},
{ message = "^ci", group = "Continuous Integration"},
{ message = "^chore", group = "Miscellaneous Tasks"},
{ body = ".*security", group = "Security"},
]
# filter out the commits that are not matched by commit parsers
@@ -3,7 +3,7 @@ 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-${version}`;
const CURRENT_VERSION = `matrix-sdk-crypto-nodejs-v${version}`;
const byteHelper = function (value) {
if (value === 0) {
@@ -1,6 +1,6 @@
{
"name": "@matrix-org/matrix-sdk-crypto-nodejs",
"version": "0.1.0-beta.0",
"version": "0.1.0-beta.1",
"main": "index.js",
"types": "index.d.ts",
"napi": {
@@ -15,7 +15,8 @@
"devDependencies": {
"@napi-rs/cli": "^2.9.0",
"jest": "^28.1.0",
"typedoc": "^0.22.17"
"typedoc": "^0.22.17",
"yargs-parser": "~21.0.1"
},
"engines": {
"node": ">= 14"
@@ -17,7 +17,7 @@ pub struct Attachment;
impl Attachment {
/// Encrypt the content of the `Uint8Array`.
///
/// It produces an `EncryptedAttachment`, we can be used to
/// It produces an `EncryptedAttachment`, which can be used to
/// retrieve the media encryption information, or the encrypted
/// data.
#[napi]
@@ -55,7 +55,7 @@ impl Attachment {
None => {
return Err(napi::Error::from_reason(
"The media encryption info are absent from the given encrypted attachment"
.to_string(),
.to_owned(),
))
}
};
@@ -16,7 +16,7 @@ pub enum EncryptionAlgorithm {
MegolmV1AesSha2,
}
impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
impl From<EncryptionAlgorithm> for matrix_sdk_crypto::types::EventEncryptionAlgorithm {
fn from(value: EncryptionAlgorithm) -> Self {
use EncryptionAlgorithm::*;
@@ -27,9 +27,9 @@ impl From<EncryptionAlgorithm> for ruma::EventEncryptionAlgorithm {
}
}
impl From<ruma::EventEncryptionAlgorithm> for EncryptionAlgorithm {
fn from(value: ruma::EventEncryptionAlgorithm) -> Self {
use ruma::EventEncryptionAlgorithm::*;
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,
@@ -58,6 +58,10 @@ pub struct EncryptionSettings {
/// 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 {
@@ -77,6 +81,7 @@ impl Default for EncryptionSettings {
n.into()
},
history_visibility: default.history_visibility.into(),
only_allow_trusted_devices: default.only_allow_trusted_devices,
}
}
}
@@ -97,6 +102,7 @@ impl From<&EncryptionSettings> for matrix_sdk_crypto::olm::EncryptionSettings {
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,
}
}
}
@@ -7,11 +7,8 @@ use std::{
use napi::bindgen_prelude::Either7;
use napi_derive::*;
use ruma::{
events::room::encrypted::OriginalSyncRoomEncryptedEvent, DeviceKeyAlgorithm,
OwnedTransactionId, UInt,
};
use serde_json::Value as JsonValue;
use ruma::{serde::Raw, DeviceKeyAlgorithm, OwnedTransactionId, UInt};
use serde_json::{value::RawValue, Value as JsonValue};
use zeroize::Zeroize;
use crate::{
@@ -68,7 +65,7 @@ impl OlmMachine {
let store = store_path
.map(|store_path| {
matrix_sdk_sled::CryptoStore::open_with_passphrase(
matrix_sdk_sled::SledCryptoStore::open_with_passphrase(
store_path,
store_passphrase.as_deref(),
)
@@ -103,8 +100,7 @@ impl OlmMachine {
/// constructor because building an `OlmMachine` is
/// asynchronous. Please use the `finalize` method.
#[napi(constructor)]
#[allow(clippy::new_ret_no_self)]
pub fn new() -> napi::Result<()> {
pub fn new() -> napi::Result<Self> {
Err(napi::Error::from_reason(
"To build an `OldMachine`, please use the `initialize` method",
))
@@ -138,8 +134,7 @@ impl OlmMachine {
///
/// # Arguments
///
/// * `to_device_events`, thhe to-device events of the current sync
/// response.
/// * `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
@@ -389,8 +384,7 @@ impl OlmMachine {
event: String,
room_id: &identifiers::RoomId,
) -> napi::Result<responses::DecryptedRoomEvent> {
let event: OriginalSyncRoomEncryptedEvent =
serde_json::from_str(event.as_str()).map_err(into_err)?;
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)?;
@@ -178,12 +178,8 @@ impl DecryptedRoomEvent {
/// 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) -> Option<Vec<String>> {
Some(match &self.encryption_info.as_ref()?.algorithm_info {
AlgorithmInfo::MegolmV1AesSha2 { forwarding_curve25519_key_chain, .. } => {
forwarding_curve25519_key_chain.clone()
}
})
pub fn forwarding_curve25519_key_chain(&self) -> Vec<String> {
vec![]
}
/// The verification state of the device that sent us the event,
@@ -196,8 +192,8 @@ impl DecryptedRoomEvent {
}
}
impl From<matrix_sdk_common::deserialized_responses::RoomEvent> for DecryptedRoomEvent {
fn from(value: matrix_sdk_common::deserialized_responses::RoomEvent) -> Self {
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 }
}
}
+9 -7
View File
@@ -1,6 +1,6 @@
[package]
name = "matrix-sdk-ffi"
version = "0.1.0"
version = "0.2.0"
edition = "2021"
homepage = "https://github.com/matrix-org/matrix-rust-sdk"
keywords = ["matrix", "chat", "messaging", "ffi"]
@@ -12,18 +12,19 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk"
[lib]
crate-type = ["staticlib"]
[build-dependencies]
uniffi_build = { version = "0.18.0", features = ["builtin-bindgen"] }
uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06", features = ["builtin-bindgen"] }
[dependencies]
anyhow = "1.0.51"
extension-trait = "1.0.1"
futures-core = "0.3.17"
futures-signals = { version = "0.3.28" }
futures-util = { version = "0.3.17", default-features = false }
matrix-sdk = { path = "../../crates/matrix-sdk", features = ["experimental-timeline", "markdown"] }
# FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce sliding-sync being exposed here..
# see https://github.com/matrix-org/matrix-rust-sdk/issues/1014
matrix-sdk = { path = "../../crates/matrix-sdk", features = ["anyhow", "experimental-timeline", "markdown", "sliding-sync", "socks"], version = "0.6.0" }
once_cell = "1.10.0"
parking_lot = "0.12.0"
sanitize-filename-reader-friendly = "2.2.1"
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1" }
@@ -31,6 +32,7 @@ thiserror = "1.0.30"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
tokio-stream = "0.1.8"
tracing = "0.1.32"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# keep in sync with uniffi dependency in matrix-sdk-crypto-ffi, and uniffi_bindgen in ffi CI job
uniffi = "0.18.0"
uniffi_macros = "0.18.0"
uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "091c3561656e72e1a4160412c83b36d98e556d06" }
+246 -30
View File
@@ -1,7 +1,10 @@
namespace sdk {
MediaSource media_source_from_url(string url);
MessageEventContent message_event_content_from_markdown(string md);
string gen_transaction_id();
namespace matrix_sdk_ffi {};
/// Cancels on drop
interface StoppableSpawn {
boolean is_cancelled();
void cancel();
};
[Error]
@@ -11,6 +14,178 @@ interface ClientError {
callback interface ClientDelegate {
void did_receive_sync_update();
void did_receive_auth_error(boolean is_soft_logout);
void did_update_restore_token();
};
dictionary RequiredState {
string key;
string value;
};
dictionary RoomSubscription {
sequence<RequiredState>? required_state;
u32? timeline_limit;
};
dictionary UpdateSummary {
sequence<string> views;
sequence<string> rooms;
};
callback interface SlidingSyncObserver {
void did_receive_sync_update(UpdateSummary summary);
};
enum SlidingSyncState {
/// Hasn't started yet
"Cold",
/// We are quickly preloading a preview of the most important rooms
"Preload",
/// We are trying to load all remaining rooms, might be in batches
"CatchingUp",
/// We are all caught up and now only sync the live responses.
"Live",
};
enum SlidingSyncMode {
/// Sync up the entire room list first
"FullSync",
/// Only ever sync the currently selected window
"Selective",
};
callback interface SlidingSyncViewStateObserver {
void did_receive_update(SlidingSyncState new_state);
};
[Enum]
interface RoomListEntry {
Empty();
Invalidated(string room_id);
Filled(string room_id);
};
[Enum]
interface SlidingSyncViewRoomsListDiff {
Replace(sequence<RoomListEntry> values);
InsertAt(
u32 index,
RoomListEntry value
);
UpdateAt(
u32 index,
RoomListEntry value
);
RemoveAt(u32 index);
Move(
u32 old_index,
u32 new_index
);
Push(RoomListEntry value);
// The following are supported by the generic VecDiff-type but
// in sliding sync effectively do not happen and thus aren't exposed
// to not pollute the API: Pop(); Clear();
};
callback interface SlidingSyncViewRoomListObserver {
void did_receive_update(SlidingSyncViewRoomsListDiff diff);
};
callback interface SlidingSyncViewRoomsCountObserver {
void did_receive_update(u32 count);
};
callback interface SlidingSyncViewRoomItemsObserver {
void did_receive_update();
};
interface SlidingSyncViewBuilder {
constructor();
[Self=ByArc]
SlidingSyncViewBuilder timeline_limit(u32 limit);
[Self=ByArc]
SlidingSyncViewBuilder sync_mode(SlidingSyncMode mode);
[Self=ByArc]
SlidingSyncViewBuilder batch_size(u32 size);
[Self=ByArc]
SlidingSyncViewBuilder name(string name);
[Self=ByArc]
SlidingSyncViewBuilder sort(sequence<string> sort);
[Self=ByArc]
SlidingSyncViewBuilder add_range(u32 from, u32 to);
[Self=ByArc]
SlidingSyncViewBuilder reset_ranges();
[Self=ByArc]
SlidingSyncViewBuilder required_state(sequence<RequiredState> required_state);
[Throws=ClientError, Self=ByArc]
SlidingSyncView build();
};
interface SlidingSyncView {
StoppableSpawn observe_room_list(SlidingSyncViewRoomListObserver observer);
StoppableSpawn observe_rooms_count(SlidingSyncViewRoomsCountObserver observer);
StoppableSpawn observe_state(SlidingSyncViewStateObserver observer);
StoppableSpawn observe_room_items(SlidingSyncViewRoomItemsObserver observer);
u32? current_room_count();
sequence<RoomListEntry> current_rooms_list();
void add_range(u32 from, u32 to);
void reset_ranges();
void set_range(u32 from, u32 to);
};
interface UnreadNotificationsCount {
boolean has_notifications();
u32 highlight_count();
u32 notification_count();
};
interface SlidingSyncRoom {
string? name();
string room_id();
Room? full_room();
boolean? is_dm();
boolean? is_initial();
boolean has_unread_notifications();
UnreadNotificationsCount unread_notifications();
// aliveness
boolean is_loading_more();
AnyMessage? latest_room_message();
};
interface SlidingSync {
void set_observer(SlidingSyncObserver? observer);
StoppableSpawn sync();
[Throws=ClientError]
void subscribe(string room_id, RoomSubscription? settings);
[Throws=ClientError]
void unsubscribe(string room_id);
SlidingSyncView? get_view(string name);
[Throws=ClientError]
SlidingSyncRoom? get_room(string room_id);
[Throws=ClientError]
sequence<SlidingSyncRoom?> get_rooms(sequence<string> room_ids);
};
interface ClientBuilder {
@@ -29,28 +204,37 @@ interface ClientBuilder {
Client build();
};
interface SlidingSyncBuilder {
[Throws=ClientError, Self=ByArc]
SlidingSyncBuilder homeserver(string url);
[Self=ByArc]
SlidingSyncBuilder add_fullsync_view();
[Self=ByArc]
SlidingSyncBuilder no_views();
[Self=ByArc]
SlidingSyncBuilder add_view(SlidingSyncView view);
[Throws=ClientError, Self=ByArc]
SlidingSync build();
};
interface Client {
void set_delegate(ClientDelegate? delegate);
[Throws=ClientError]
void login(string username, string password);
void login(string username, string password, string? initial_device_name, string? device_id);
[Throws=ClientError]
void restore_login(string restore_token);
string homeserver();
void start_sync();
void start_sync(u16? timeline_limit);
[Throws=ClientError]
string restore_token();
boolean is_guest();
boolean has_first_synced();
boolean is_syncing();
[Throws=ClientError]
string user_id();
@@ -63,19 +247,40 @@ interface Client {
[Throws=ClientError]
string device_id();
sequence<Room> rooms();
[Throws=ClientError]
string? account_data(string event_type);
[Throws=ClientError]
void set_account_data(string event_type, string content);
[Throws=ClientError]
sequence<u8> get_media_content(MediaSource source);
[Throws=ClientError]
sequence<u8> get_media_thumbnail(MediaSource source, u64 width, u64 height);
[Throws=ClientError]
SessionVerificationController get_session_verification_controller();
[Throws=ClientError]
SlidingSync full_sliding_sync();
SlidingSyncBuilder sliding_sync();
[Throws=ClientError]
void logout();
};
callback interface RoomDelegate {
void did_receive_message(AnyMessage message);
};
enum Membership {
"Invited",
"Joined",
"Left",
};
interface Room {
void set_delegate(RoomDelegate? delegate);
@@ -84,6 +289,8 @@ interface Room {
string? topic();
string? avatar_url();
Membership membership();
boolean is_direct();
boolean is_public();
boolean is_space();
@@ -103,14 +310,20 @@ interface Room {
void stop_live_event_listener();
[Throws=ClientError]
void send(MessageEventContent msg, string? txn_id);
void send(RoomMessageEventContent msg, string? txn_id);
[Throws=ClientError]
void send_reply(string msg, string in_reply_to_event_id, string? txn_id);
[Throws=ClientError]
void redact(string event_id, string? reason, string? txn_id);
};
interface BackwardsStream {
sequence<AnyMessage> paginate_backwards(u64 count);
};
interface MessageEventContent {};
interface RoomMessageEventContent {};
interface AnyMessage {
TextMessage? text_message();
@@ -156,27 +369,30 @@ interface MediaSource {
[Error]
enum AuthenticationError {
"ClientMissing",
"Generic",
"ClientMissing",
"SessionMissing",
"Generic",
};
interface HomeserverLoginDetails {
string url();
string? authentication_issuer();
boolean supports_password_login();
};
interface AuthenticationService {
constructor(string base_path);
[Throws=AuthenticationError]
string homeserver();
[Throws=AuthenticationError]
string? authentication_issuer();
HomeserverLoginDetails? homeserver_details();
[Throws=AuthenticationError]
boolean supports_password_login();
void configure_homeserver(string server_name);
[Throws=AuthenticationError]
void use_server(string server_name);
Client login(string username, string password, string? initial_device_name, string? device_id);
[Throws=AuthenticationError]
Client login(string username, string password);
Client restore_with_access_token(string token, string device_id);
};
interface SessionVerificationEmoji {
@@ -1,18 +1,25 @@
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use parking_lot::RwLock;
use futures_util::future::join3;
use matrix_sdk::{
ruma::{OwnedDeviceId, UserId},
Session,
};
use super::{client::Client, client_builder::ClientBuilder};
use super::{client::Client, client_builder::ClientBuilder, RUNTIME};
pub struct AuthenticationService {
base_path: String,
client: RwLock<Option<Arc<Client>>>,
homeserver_details: RwLock<Option<Arc<HomeserverLoginDetails>>>,
}
#[derive(Debug, thiserror::Error)]
pub enum AuthenticationError {
#[error("A successful call to use_server must be made first.")]
ClientMissing,
#[error("Login was successful but is missing a valid Session to configure the file store.")]
SessionMissing,
#[error("An error occurred: {message}")]
Generic { message: String },
}
@@ -23,52 +30,65 @@ impl From<anyhow::Error> for AuthenticationError {
}
}
impl AuthenticationService {
/// Creates a new service to authenticate a user with.
pub fn new(base_path: String) -> Self {
AuthenticationService { base_path, client: RwLock::new(None) }
}
pub struct HomeserverLoginDetails {
url: String,
authentication_issuer: Option<String>,
supports_password_login: bool,
}
/// The currently configured homeserver.
pub fn homeserver(&self) -> Result<String, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.map(|client| client.homeserver())
impl HomeserverLoginDetails {
/// The URL of the currently configured homeserver.
pub fn url(&self) -> String {
self.url.clone()
}
/// The OIDC Provider that is trusted by the homeserver. `None` when
/// not configured.
pub fn authentication_issuer(&self) -> Result<Option<String>, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.map(|client| client.authentication_issuer())
pub fn authentication_issuer(&self) -> Option<String> {
self.authentication_issuer.clone()
}
/// Whether the current homeserver supports the password login flow.
pub fn supports_password_login(&self) -> Result<bool, AuthenticationError> {
self.client
.read()
.as_ref()
.ok_or(AuthenticationError::ClientMissing)
.and_then(|client| client.supports_password_login().map_err(AuthenticationError::from))
pub fn supports_password_login(&self) -> bool {
self.supports_password_login
}
}
impl AuthenticationService {
/// Creates a new service to authenticate a user with.
pub fn new(base_path: String) -> Self {
AuthenticationService {
base_path,
client: RwLock::new(None),
homeserver_details: RwLock::new(None),
}
}
/// Updates the server to authenticate with the specified homeserver.
pub fn use_server(&self, server_name: String) -> Result<(), AuthenticationError> {
// Construct a username as the builder currently requires one.
let username = format!("@auth:{}", server_name);
let client = Arc::new(ClientBuilder::new())
.base_path(self.base_path.clone())
.username(username)
.build()
.map_err(AuthenticationError::from)?;
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
self.homeserver_details.read().unwrap().clone()
}
*self.client.write() = Some(client);
Ok(())
/// Updates the service to authenticate with the homeserver for the
/// specified address.
pub fn configure_homeserver(&self, server_name: String) -> Result<(), AuthenticationError> {
let mut builder = Arc::new(ClientBuilder::new()).base_path(self.base_path.clone());
if server_name.starts_with("http://") || server_name.starts_with("https://") {
builder = builder.homeserver_url(server_name)
} else {
builder = builder.server_name(server_name);
}
let client = builder.build().map_err(AuthenticationError::from)?;
RUNTIME.block_on(async move {
let details = Arc::new(self.details_from_client(&client).await?);
*self.client.write().unwrap() = Some(client);
*self.homeserver_details.write().unwrap() = Some(details);
Ok(())
})
}
/// Performs a password login using the current homeserver.
@@ -76,25 +96,105 @@ impl AuthenticationService {
&self,
username: String,
password: String,
initial_device_name: Option<String>,
device_id: Option<String>,
) -> Result<Arc<Client>, AuthenticationError> {
match self.client.read().as_ref() {
Some(client) => {
let homeserver_url = client.homeserver();
let client = match &*self.client.read().unwrap() {
Some(client) => client.clone(),
None => return Err(AuthenticationError::ClientMissing),
};
// Create a new client to setup the store path for the username
let client = Arc::new(ClientBuilder::new())
.base_path(self.base_path.clone())
.homeserver_url(homeserver_url)
.username(username.clone())
.build()
.map_err(AuthenticationError::from)?;
// Login and ask the server for the full user ID as this could be different from
// the username that was entered.
client
.login(username, password, initial_device_name, device_id)
.map_err(AuthenticationError::from)?;
let whoami = client.whoami()?;
client
.login(username, password)
.map(|_| client.clone())
.map_err(AuthenticationError::from)
}
None => Err(AuthenticationError::ClientMissing),
}
// Create a new client to setup the store path now the user ID is known.
let homeserver_url = client.homeserver();
let session = client.session().ok_or(AuthenticationError::SessionMissing)?;
let client = Arc::new(ClientBuilder::new())
.base_path(self.base_path.clone())
.homeserver_url(homeserver_url)
.username(whoami.user_id.to_string())
.build()
.map_err(AuthenticationError::from)?;
// Restore the client using the session from the login request.
client.restore_session(session).map_err(AuthenticationError::from)?;
Ok(client)
}
/// Restore an existing session on the current homeserver using an access
/// token issued by an authentication server.
/// # Arguments
///
/// * `token` - The access token issued by the authentication server.
///
/// * `device_id` - The device ID that the access token was scoped for.
pub fn restore_with_access_token(
&self,
token: String,
device_id: String,
) -> Result<Arc<Client>, AuthenticationError> {
let client = match &*self.client.read().unwrap() {
Some(client) => client.clone(),
None => return Err(AuthenticationError::ClientMissing),
};
// Restore the client and ask the server for the full user ID as this
// could be different from the username that was entered.
let discovery_user_id = UserId::parse("@unknown:unknown")
.map_err(|e| AuthenticationError::Generic { message: e.to_string() })?;
let device_id: OwnedDeviceId = device_id.as_str().into();
let discovery_session = Session {
access_token: token.clone(),
refresh_token: None,
user_id: discovery_user_id,
device_id: device_id.clone(),
};
client.restore_session(discovery_session).map_err(AuthenticationError::from)?;
let whoami = client.whoami()?;
// Create the actual client with a store path from the user ID.
let homeserver_url = client.homeserver();
let session = Session {
access_token: token,
refresh_token: None,
user_id: whoami.user_id.clone(),
device_id,
};
let client = Arc::new(ClientBuilder::new())
.base_path(self.base_path.clone())
.homeserver_url(homeserver_url)
.username(whoami.user_id.to_string())
.build()
.map_err(AuthenticationError::from)?;
// Restore the client using the session.
client.restore_session(session).map_err(AuthenticationError::from)?;
Ok(client)
}
/// Get the homeserver login details from a client.
async fn details_from_client(
&self,
client: &Arc<Client>,
) -> Result<HomeserverLoginDetails, AuthenticationError> {
let login_details = join3(
client.async_homeserver(),
client.authentication_issuer(),
client.supports_password_login(),
)
.await;
let url = login_details.0;
let authentication_issuer = login_details.1;
let supports_password_login = login_details.2.map_err(AuthenticationError::from)?;
Ok(HomeserverLoginDetails { url, authentication_issuer, supports_password_login })
}
}
@@ -2,7 +2,7 @@ use core::pin::Pin;
use std::sync::Arc;
use futures_core::Stream;
use matrix_sdk::{deserialized_responses::SyncRoomEvent, locks::Mutex, Result};
use matrix_sdk::{deserialized_responses::SyncTimelineEvent, locks::Mutex, Result};
use tokio_stream::StreamExt;
use tracing::error;
@@ -11,7 +11,7 @@ use super::{
RUNTIME,
};
type MsgStream = Pin<Box<dyn Stream<Item = Result<SyncRoomEvent>> + Send>>;
type MsgStream = Pin<Box<dyn Stream<Item = Result<SyncTimelineEvent>> + Send>>;
pub struct BackwardsStream {
stream: Arc<Mutex<MsgStream>>,
+213 -76
View File
@@ -1,20 +1,27 @@
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use anyhow::anyhow;
use matrix_sdk::{
config::SyncSettings,
media::{MediaFormat, MediaRequest},
media::{MediaFormat, MediaRequest, MediaThumbnailSize},
ruma::{
api::client::{
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
session::get_login_types,
sync::sync_events::v3::Filter,
api::{
client::{
account::whoami,
error::ErrorKind,
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
media::get_content_thumbnail::v3::Method,
session::get_login_types,
sync::sync_events::v3::Filter,
},
error::{FromHttpResponseError, ServerError},
},
events::room::MediaSource,
TransactionId,
serde::Raw,
TransactionId, UInt,
},
Client as MatrixClient, LoopCtrl,
Client as MatrixClient, Error, HttpError, LoopCtrl, RumaApiError, Session,
};
use parking_lot::RwLock;
use super::{
room::Room, session_verification::SessionVerificationController, ClientState, RestoreToken,
@@ -30,11 +37,13 @@ impl std::ops::Deref for Client {
pub trait ClientDelegate: Sync + Send {
fn did_receive_sync_update(&self);
fn did_receive_auth_error(&self, is_soft_logout: bool);
fn did_update_restore_token(&self);
}
#[derive(Clone)]
pub struct Client {
client: MatrixClient,
pub(crate) client: MatrixClient,
state: Arc<RwLock<ClientState>>,
delegate: Arc<RwLock<Option<Box<dyn ClientDelegate>>>>,
session_verification_controller:
@@ -51,17 +60,40 @@ impl Client {
}
}
pub fn login(&self, username: String, password: String) -> anyhow::Result<()> {
/// Login using a username and password.
pub fn login(
&self,
username: String,
password: String,
initial_device_name: Option<String>,
device_id: Option<String>,
) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
self.client.login_username(&username, &password).send().await?;
let mut builder = self.client.login_username(&username, &password);
if let Some(initial_device_name) = initial_device_name.as_ref() {
builder = builder.initial_device_display_name(initial_device_name);
}
if let Some(device_id) = device_id.as_ref() {
builder = builder.device_id(device_id);
}
builder.send().await?;
Ok(())
})
}
/// Restores the client from a `RestoreToken`.
pub fn restore_login(&self, restore_token: String) -> anyhow::Result<()> {
let RestoreToken { session, homeurl: _, is_guest: _ } =
let RestoreToken { session, homeurl: _, is_guest: _, is_soft_logout } =
serde_json::from_str(&restore_token)?;
// update soft logout state
self.state.write().unwrap().is_soft_logout = is_soft_logout;
self.restore_session(session)
}
/// Restores the client from a `Session`.
pub fn restore_session(&self, session: Session) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
self.client.restore_login(session).await?;
Ok(())
@@ -69,115 +101,108 @@ impl Client {
}
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) {
*self.delegate.write() = delegate;
*self.delegate.write().unwrap() = delegate;
}
/// The homeserver this client is configured to use.
pub fn homeserver(&self) -> String {
RUNTIME.block_on(async move { self.client.homeserver().await.to_string() })
pub async fn async_homeserver(&self) -> String {
self.client.homeserver().await.to_string()
}
/// The OIDC Provider that is trusted by the homeserver. `None` when
/// not configured.
pub fn authentication_issuer(&self) -> Option<String> {
RUNTIME.block_on(async move {
self.client.authentication_issuer().await.map(|server| server.to_string())
})
pub async fn authentication_issuer(&self) -> Option<String> {
self.client.authentication_issuer().await.map(|server| server.to_string())
}
/// Whether or not the client's homeserver supports the password login flow.
pub fn supports_password_login(&self) -> anyhow::Result<bool> {
RUNTIME.block_on(async move {
let login_types = self.client.get_login_types().await?;
let supports_password = login_types.flows.iter().any(|login_type| {
matches!(login_type, get_login_types::v3::LoginType::Password(_))
});
Ok(supports_password)
})
pub async fn supports_password_login(&self) -> anyhow::Result<bool> {
let login_types = self.client.get_login_types().await?;
let supports_password = login_types
.flows
.iter()
.any(|login_type| matches!(login_type, get_login_types::v3::LoginType::Password(_)));
Ok(supports_password)
}
pub fn start_sync(&self) {
/// Gets information about the owner of a given access token.
pub fn whoami(&self) -> anyhow::Result<whoami::v3::Response> {
RUNTIME
.block_on(async move { self.client.whoami().await.map_err(|e| anyhow!(e.to_string())) })
}
pub fn start_sync(&self, timeline_limit: Option<u16>) {
let client = self.client.clone();
let state = self.state.clone();
let delegate = self.delegate.clone();
let session_verification_controller = self.session_verification_controller.clone();
let local_self = self.clone();
RUNTIME.spawn(async move {
let mut filter = FilterDefinition::default();
let mut room_filter = RoomFilter::default();
let mut event_filter = RoomEventFilter::default();
let mut timeline_filter = RoomEventFilter::default();
event_filter.lazy_load_options =
LazyLoadOptions::Enabled { include_redundant_members: false };
room_filter.state = event_filter;
filter.room = room_filter;
timeline_filter.limit = timeline_limit.map(|limit| limit.into());
filter.room.timeline = timeline_filter;
let filter_id = client.get_or_upload_filter("sync", filter).await.unwrap();
let sync_settings = SyncSettings::new().filter(Filter::FilterId(&filter_id));
client
.sync_with_callback(sync_settings, |sync_response| async {
if !state.read().has_first_synced {
state.write().has_first_synced = true
}
.sync_with_result_callback(sync_settings, |result| async {
Ok(if let Ok(sync_response) = result {
if !state.read().unwrap().has_first_synced {
state.write().unwrap().has_first_synced = true;
}
if state.read().should_stop_syncing {
state.write().is_syncing = false;
return LoopCtrl::Break;
} else if !state.read().is_syncing {
state.write().is_syncing = true;
}
if state.read().unwrap().should_stop_syncing {
state.write().unwrap().is_syncing = false;
return Ok(LoopCtrl::Break);
} else if !state.read().unwrap().is_syncing {
state.write().unwrap().is_syncing = true;
}
if let Some(delegate) = &*delegate.read() {
delegate.did_receive_sync_update()
}
if let Some(delegate) = &*delegate.read().unwrap() {
delegate.did_receive_sync_update()
}
if let Some(session_verification_controller) =
&*session_verification_controller.read().await
{
session_verification_controller
.process_to_device_messages(sync_response.to_device)
.await;
}
if let Some(session_verification_controller) =
&*session_verification_controller.read().await
{
session_verification_controller
.process_to_device_messages(sync_response.to_device)
.await;
}
LoopCtrl::Continue
LoopCtrl::Continue
} else {
local_self.process_sync_error(result.err().unwrap())
})
})
.await;
.await
.unwrap();
});
}
/// Indication whether we've received a first sync response since
/// establishing the client (in memory)
pub fn has_first_synced(&self) -> bool {
self.state.read().has_first_synced
}
/// Indication whether we are currently syncing
pub fn is_syncing(&self) -> bool {
self.state.read().has_first_synced
}
/// Is this a guest account?
pub fn is_guest(&self) -> bool {
self.state.read().is_guest
}
pub fn restore_token(&self) -> anyhow::Result<String> {
RUNTIME.block_on(async move {
let session = self.client.session().expect("Missing session").clone();
let session = self.client.session().expect("Missing session");
let homeurl = self.client.homeserver().await.into();
Ok(serde_json::to_string(&RestoreToken {
session,
homeurl,
is_guest: self.state.read().is_guest,
is_guest: self.state.read().unwrap().is_guest,
is_soft_logout: self.state.read().unwrap().is_soft_logout,
})?)
})
}
pub fn rooms(&self) -> Vec<Arc<Room>> {
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
}
pub fn user_id(&self) -> anyhow::Result<String> {
let user_id = self.client.user_id().expect("No User ID found");
Ok(user_id.to_string())
@@ -204,12 +229,61 @@ impl Client {
Ok(device_id.to_string())
}
/// Get the content of the event of the given type out of the account data
/// store.
///
/// It will be returned as a JSON string.
pub fn account_data(&self, event_type: String) -> anyhow::Result<Option<String>> {
RUNTIME.block_on(async move {
let event = self.client.account().account_data_raw(event_type.into()).await?;
Ok(event.map(|e| e.json().get().to_owned()))
})
}
/// Set the given account data content for the given event type.
///
/// It should be supplied as a JSON string.
pub fn set_account_data(&self, event_type: String, content: String) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
let raw_content = Raw::from_json_string(content)?;
self.client.account().set_account_data_raw(event_type.into(), raw_content).await?;
Ok(())
})
}
pub fn get_media_content(&self, media_source: Arc<MediaSource>) -> anyhow::Result<Vec<u8>> {
let l = self.client.clone();
let source = (*media_source).clone();
RUNTIME.block_on(async move {
Ok(l.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
Ok(l.media()
.get_media_content(&MediaRequest { source, format: MediaFormat::File }, true)
.await?)
})
}
pub fn get_media_thumbnail(
&self,
media_source: Arc<MediaSource>,
width: u64,
height: u64,
) -> anyhow::Result<Vec<u8>> {
let l = self.client.clone();
let source = (*media_source).clone();
RUNTIME.block_on(async move {
Ok(l.media()
.get_media_content(
&MediaRequest {
source,
format: MediaFormat::Thumbnail(MediaThumbnailSize {
method: Method::Scale,
width: UInt::new(width).unwrap(),
height: UInt::new(height).unwrap(),
}),
},
true,
)
.await?)
})
}
@@ -240,8 +314,71 @@ impl Client {
Ok(Arc::new(session_verification_controller))
})
}
/// Log out the current user
pub fn logout(&self) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
match self.client.logout().await {
Ok(_) => Ok(()),
Err(error) => Err(anyhow!(error.to_string())),
}
})
}
/// Process a sync error and return loop control accordingly
fn process_sync_error(&self, sync_error: Error) -> LoopCtrl {
let mut control = LoopCtrl::Continue;
if let Error::Http(HttpError::Api(FromHttpResponseError::Server(ServerError::Known(
RumaApiError::ClientApi(error),
)))) = sync_error
{
if let ErrorKind::UnknownToken { soft_logout } = error.kind {
self.state.write().unwrap().is_soft_logout = soft_logout;
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_update_restore_token();
delegate.did_receive_auth_error(soft_logout);
}
control = LoopCtrl::Break
}
}
control
}
}
pub fn gen_transaction_id() -> String {
#[uniffi::export]
impl Client {
/// The homeserver this client is configured to use.
pub fn homeserver(&self) -> String {
RUNTIME.block_on(async move { self.async_homeserver().await })
}
/// Indication whether we've received a first sync response since
/// establishing the client (in memory)
pub fn has_first_synced(&self) -> bool {
self.state.read().unwrap().has_first_synced
}
/// Indication whether we are currently syncing
pub fn is_syncing(&self) -> bool {
self.state.read().unwrap().has_first_synced
}
/// Is this a guest account?
pub fn is_guest(&self) -> bool {
self.state.read().unwrap().is_guest
}
/// Flag indicating whether the session is in soft logout mode
pub fn is_soft_logout(&self) -> bool {
self.state.read().unwrap().is_soft_logout
}
pub fn rooms(&self) -> Vec<Arc<Room>> {
self.client.rooms().into_iter().map(|room| Arc::new(Room::new(room))).collect()
}
}
#[uniffi::export]
fn gen_transaction_id() -> String {
TransactionId::new().to_string()
}
+30 -19
View File
@@ -1,18 +1,21 @@
use std::{fs, path::PathBuf, sync::Arc};
use anyhow::Context;
use anyhow::anyhow;
use matrix_sdk::{
ruma::UserId, store::make_store_config, Client as MatrixClient,
ClientBuilder as MatrixClientBuilder,
ruma::{ServerName, UserId},
store::make_store_config,
Client as MatrixClient, ClientBuilder as MatrixClientBuilder,
};
use sanitize_filename_reader_friendly::sanitize;
use super::{client::Client, ClientState, RUNTIME};
use crate::helpers::unwrap_or_clone_arc;
#[derive(Clone)]
pub struct ClientBuilder {
base_path: Option<String>,
username: Option<String>,
server_name: Option<String>,
homeserver_url: Option<String>,
inner: MatrixClientBuilder,
}
@@ -22,6 +25,7 @@ impl ClientBuilder {
Self {
base_path: None,
username: None,
server_name: None,
homeserver_url: None,
inner: MatrixClient::builder().user_agent("rust-sdk-ios"),
}
@@ -39,6 +43,12 @@ impl ClientBuilder {
Arc::new(builder)
}
pub fn server_name(self: Arc<Self>, server_name: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.server_name = Some(server_name);
Arc::new(builder)
}
pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.homeserver_url = Some(url);
@@ -47,25 +57,30 @@ impl ClientBuilder {
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> {
let builder = unwrap_or_clone_arc(self);
let mut inner_builder = builder.inner;
let base_path = builder.base_path.context("Base path was not set")?;
let username = builder
.username
.context("Username to determine homeserver and home path was not set")?;
if let (Some(base_path), Some(username)) = (builder.base_path, &builder.username) {
// Determine store path
let data_path = PathBuf::from(base_path).join(sanitize(username));
fs::create_dir_all(&data_path)?;
let store_config = make_store_config(&data_path, None)?;
// Determine store path
let data_path = PathBuf::from(base_path).join(sanitize(&username));
fs::create_dir_all(&data_path)?;
let store_config = make_store_config(&data_path, None)?;
inner_builder = inner_builder.store_config(store_config);
}
let mut inner_builder = builder.inner.store_config(store_config);
// Determine server either from explicitly set homeserver or from userId
// Determine server either from URL, server name or user ID.
if let Some(homeserver_url) = builder.homeserver_url {
inner_builder = inner_builder.homeserver_url(homeserver_url);
} else {
} else if let Some(server_name) = builder.server_name {
let server_name = ServerName::parse(server_name)?;
inner_builder = inner_builder.server_name(&server_name);
} else if let Some(username) = builder.username {
let user = UserId::parse(username)?;
inner_builder = inner_builder.server_name(user.server_name());
} else {
return Err(anyhow!(
"Failed to build: One of homeserver_url, server_name or username must be called."
));
}
RUNTIME.block_on(async move {
@@ -81,7 +96,3 @@ impl Default for ClientBuilder {
Self::new()
}
}
fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone())
}
+5
View File
@@ -0,0 +1,5 @@
use std::sync::Arc;
pub(crate) fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T {
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone())
}
+21 -1
View File
@@ -6,17 +6,22 @@ pub mod authentication_service;
pub mod backward_stream;
pub mod client;
pub mod client_builder;
mod helpers;
pub mod messages;
pub mod room;
pub mod session_verification;
pub mod sliding_sync;
mod uniffi_api;
use std::io;
use client::Client;
use client_builder::ClientBuilder;
use matrix_sdk::Session;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use tokio::runtime::Runtime;
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
pub use uniffi_api::*;
pub static RUNTIME: Lazy<Runtime> =
@@ -26,7 +31,7 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId};
pub use self::{
authentication_service::*, backward_stream::*, client::*, messages::*, room::*,
session_verification::*,
session_verification::*, sliding_sync::*,
};
#[derive(Default, Debug)]
@@ -35,6 +40,7 @@ pub struct ClientState {
has_first_synced: bool,
is_syncing: bool,
should_stop_syncing: bool,
is_soft_logout: bool,
}
#[derive(Serialize, Deserialize)]
@@ -42,6 +48,8 @@ struct RestoreToken {
is_guest: bool,
homeurl: String,
session: Session,
#[serde(default)]
is_soft_logout: bool,
}
#[derive(thiserror::Error, Debug)]
@@ -55,3 +63,15 @@ impl From<anyhow::Error> for ClientError {
ClientError::Generic { msg: e.to_string() }
}
}
#[uniffi::export]
fn setup_tracing(configuration: String) {
tracing_subscriber::registry()
.with(EnvFilter::new(configuration))
.with(fmt::layer().with_ansi(false).with_writer(io::stderr))
.init();
}
mod uniffi_types {
pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource};
}
+9 -9
View File
@@ -1,17 +1,15 @@
use std::sync::Arc;
use extension_trait::extension_trait;
pub use matrix_sdk::ruma::events::room::{
message::RoomMessageEventContent as MessageEventContent, MediaSource,
};
pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource};
use matrix_sdk::{
deserialized_responses::SyncRoomEvent,
deserialized_responses::SyncTimelineEvent,
ruma::events::{
room::{
message::{ImageMessageEventContent, MessageFormat, MessageType},
ImageInfo,
},
AnySyncMessageLikeEvent, AnySyncRoomEvent, SyncMessageLikeEvent,
AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
},
};
@@ -144,9 +142,9 @@ impl AnyMessage {
}
}
pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option<Arc<AnyMessage>> {
pub fn sync_event_to_message(sync_event: SyncTimelineEvent) -> Option<Arc<AnyMessage>> {
match sync_event.event.deserialize() {
Ok(AnySyncRoomEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
SyncMessageLikeEvent::Original(m),
))) => {
let base_message = Arc::new(BaseMessage {
@@ -235,12 +233,14 @@ pub fn sync_event_to_message(sync_event: SyncRoomEvent) -> Option<Arc<AnyMessage
}
}
#[uniffi::export]
pub fn media_source_from_url(url: String) -> Arc<MediaSource> {
Arc::new(MediaSource::Plain(url.into()))
}
pub fn message_event_content_from_markdown(md: String) -> Arc<MessageEventContent> {
Arc::new(MessageEventContent::text_markdown(md))
#[uniffi::export]
pub fn message_event_content_from_markdown(md: String) -> Arc<RoomMessageEventContent> {
Arc::new(RoomMessageEventContent::text_markdown(md))
}
#[extension_trait]
+93 -13
View File
@@ -1,12 +1,17 @@
use std::sync::Arc;
use std::{
convert::TryFrom,
sync::{Arc, RwLock},
};
use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::{
room::Room as MatrixRoom,
ruma::{events::room::message::RoomMessageEventContent, UserId},
ruma::{
events::room::message::{RoomMessageEvent, RoomMessageEventContent},
EventId, UserId,
},
};
use parking_lot::RwLock;
use super::{
backward_stream::BackwardsStream,
@@ -18,6 +23,12 @@ pub trait RoomDelegate: Sync + Send {
fn did_receive_message(&self, messages: Arc<AnyMessage>);
}
pub enum Membership {
Invited,
Joined,
Left,
}
pub struct Room {
room: MatrixRoom,
delegate: Arc<RwLock<Option<Box<dyn RoomDelegate>>>>,
@@ -34,7 +45,7 @@ impl Room {
}
pub fn set_delegate(&self, delegate: Option<Box<dyn RoomDelegate>>) {
*self.delegate.write() = delegate;
*self.delegate.write().unwrap() = delegate;
}
pub fn id(&self) -> String {
@@ -80,6 +91,14 @@ impl Room {
})
}
pub fn membership(&self) -> Membership {
match &self.room {
MatrixRoom::Invited(_) => Membership::Invited,
MatrixRoom::Joined(_) => Membership::Joined,
MatrixRoom::Left(_) => Membership::Left,
}
}
pub fn is_direct(&self) -> bool {
self.room.is_direct()
}
@@ -101,11 +120,11 @@ impl Room {
}
pub fn start_live_event_listener(&self) -> Option<Arc<BackwardsStream>> {
if *self.is_listening_to_live_events.read() {
if *self.is_listening_to_live_events.read().unwrap() {
return None;
}
*self.is_listening_to_live_events.write() = true;
*self.is_listening_to_live_events.write().unwrap() = true;
let room = self.room.clone();
let delegate = self.delegate.clone();
@@ -119,11 +138,11 @@ impl Room {
pin_mut!(forward_stream);
while let Some(sync_event) = forward_stream.next().await {
if !(*is_listening_to_live_events.read()) {
if !(*is_listening_to_live_events.read().unwrap()) {
return;
}
if let Some(delegate) = &*delegate.read() {
if let Some(delegate) = &*delegate.read().unwrap() {
if let Some(message) = sync_event_to_message(sync_event) {
delegate.did_receive_message(message)
}
@@ -134,7 +153,7 @@ impl Room {
}
pub fn stop_live_event_listener(&self) {
*self.is_listening_to_live_events.write() = false;
*self.is_listening_to_live_events.write().unwrap() = false;
}
pub fn send(&self, msg: Arc<RoomMessageEventContent>, txn_id: Option<String>) -> Result<()> {
@@ -144,10 +163,71 @@ impl Room {
};
RUNTIME.block_on(async move {
room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await
})?;
room.send((*msg).to_owned(), txn_id.as_deref().map(Into::into)).await?;
Ok(())
})
}
Ok(())
pub fn send_reply(
&self,
msg: String,
in_reply_to_event_id: String,
txn_id: Option<String>,
) -> Result<()> {
let room = match &self.room {
MatrixRoom::Joined(j) => j.clone(),
_ => bail!("Can't send to a room that isn't in joined state"),
};
let event_id: &EventId =
in_reply_to_event_id.as_str().try_into().context("Failed to create EventId.")?;
RUNTIME.block_on(async move {
let timeline_event = room.event(event_id).await.context("Couldn't find event.")?;
let event_content = timeline_event
.event
.deserialize_as::<RoomMessageEvent>()
.context("Couldn't deserialise event")?;
let original_message =
event_content.as_original().context("Couldn't retrieve original message.")?;
let reply_content =
RoomMessageEventContent::text_markdown(msg).make_reply_to(original_message);
room.send(reply_content, txn_id.as_deref().map(Into::into)).await?;
Ok(())
})
}
/// Redacts an event from the room.
///
/// # Arguments
///
/// * `event_id` - The ID of the event to redact
///
/// * `reason` - The reason for the event being redacted (optional).
///
/// * `txn_id` - A unique ID that can be attached to this event as
/// its transaction ID (optional). If not given one is created.
pub fn redact(
&self,
event_id: String,
reason: Option<String>,
txn_id: Option<String>,
) -> Result<()> {
let room = match &self.room {
MatrixRoom::Joined(j) => j.clone(),
_ => bail!("Can't redact in a room that isn't in joined state"),
};
RUNTIME.block_on(async move {
let event_id = EventId::parse(event_id)?;
room.redact(&event_id, reason.as_deref(), txn_id.map(Into::into)).await?;
Ok(())
})
}
}
@@ -1,4 +1,4 @@
use std::sync::Arc;
use std::sync::{Arc, RwLock};
use matrix_sdk::{
encryption::{
@@ -10,7 +10,6 @@ use matrix_sdk::{
events::{key::verification::VerificationMethod, AnyToDeviceEvent},
},
};
use parking_lot::RwLock;
use super::RUNTIME;
@@ -55,11 +54,11 @@ impl SessionVerificationController {
}
pub fn set_delegate(&self, delegate: Option<Box<dyn SessionVerificationControllerDelegate>>) {
*self.delegate.write() = delegate;
*self.delegate.write().unwrap() = delegate;
}
pub fn is_verified(&self) -> bool {
self.user_identity.verified()
self.user_identity.is_verified()
}
pub fn request_verification(&self) -> anyhow::Result<()> {
@@ -67,7 +66,7 @@ impl SessionVerificationController {
let methods = vec![VerificationMethod::SasV1];
let verification_request =
self.user_identity.request_verification_with_methods(methods).await?;
*self.verification_request.write() = Some(verification_request);
*self.verification_request.write().unwrap() = Some(verification_request);
Ok(())
})
@@ -75,7 +74,7 @@ impl SessionVerificationController {
pub fn approve_verification(&self) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
let sas_verification = self.sas_verification.read().clone();
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
sas_verification.confirm().await?;
}
@@ -86,7 +85,7 @@ impl SessionVerificationController {
pub fn decline_verification(&self) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
let sas_verification = self.sas_verification.read().clone();
let sas_verification = self.sas_verification.read().unwrap().clone();
if let Some(sas_verification) = sas_verification {
sas_verification.mismatch().await?;
}
@@ -97,7 +96,7 @@ impl SessionVerificationController {
pub fn cancel_verification(&self) -> anyhow::Result<()> {
RUNTIME.block_on(async move {
let verification_request = self.verification_request.read().clone();
let verification_request = self.verification_request.read().unwrap().clone();
if let Some(verification) = verification_request {
verification.cancel().await?;
}
@@ -122,7 +121,7 @@ impl SessionVerificationController {
return;
}
if let Some(delegate) = &*self.delegate.read() {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_cancel()
}
}
@@ -131,9 +130,9 @@ impl SessionVerificationController {
return;
}
if let Some(sas_verification) = &*sas_verification.read() {
if let Some(sas_verification) = &*sas_verification.read().unwrap() {
if let Some(emojis) = sas_verification.emoji() {
if let Some(delegate) = &*self.delegate.read() {
if let Some(delegate) = &*self.delegate.read().unwrap() {
let emojis = emojis
.iter()
.map(|e| {
@@ -146,10 +145,10 @@ impl SessionVerificationController {
delegate.did_receive_verification_data(emojis);
}
} else if let Some(delegate) = &*self.delegate.read() {
} else if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}
} else if let Some(delegate) = &*self.delegate.read() {
} else if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}
}
@@ -158,7 +157,7 @@ impl SessionVerificationController {
return;
}
if let Some(delegate) = &*self.delegate.read() {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_finish()
}
}
@@ -168,7 +167,7 @@ impl SessionVerificationController {
}
fn is_transaction_id_valid(&self, transaction_id: String) -> bool {
if let Some(verification) = &*self.verification_request.read() {
if let Some(verification) = &*self.verification_request.read().unwrap() {
return verification.flow_id() == transaction_id;
}
@@ -176,14 +175,14 @@ impl SessionVerificationController {
}
async fn start_sas_verification(&self) {
let verification_request = self.verification_request.read().clone();
let verification_request = self.verification_request.read().unwrap().clone();
if let Some(verification) = verification_request {
match verification.start_sas().await {
Ok(verification) => {
*self.sas_verification.write() = verification;
*self.sas_verification.write().unwrap() = verification;
}
Err(_) => {
if let Some(delegate) = &*self.delegate.read() {
if let Some(delegate) = &*self.delegate.read().unwrap() {
delegate.did_fail()
}
}
+582
View File
@@ -0,0 +1,582 @@
use std::sync::{Arc, RwLock};
use futures_signals::{
signal::SignalExt,
signal_vec::{SignalVecExt, VecDiff},
};
use futures_util::{pin_mut, StreamExt};
use matrix_sdk::ruma::{
api::client::sync::sync_events::{
v4::RoomSubscription as RumaRoomSubscription,
UnreadNotificationsCount as RumaUnreadNotificationsCount,
},
assign, IdParseError, OwnedRoomId,
};
pub use matrix_sdk::{
Client as MatrixClient, RoomListEntry as MatrixRoomEntry,
SlidingSyncBuilder as MatrixSlidingSyncBuilder, SlidingSyncMode, SlidingSyncState,
};
use tokio::task::JoinHandle;
use super::{Client, RUNTIME};
use crate::helpers::unwrap_or_clone_arc;
pub struct StoppableSpawn {
handle: Arc<RwLock<Option<JoinHandle<()>>>>,
}
impl StoppableSpawn {
fn with_handle(handle: JoinHandle<()>) -> StoppableSpawn {
StoppableSpawn { handle: Arc::new(RwLock::new(Some(handle))) }
}
fn with_handle_ref(handle: Arc<RwLock<Option<JoinHandle<()>>>>) -> StoppableSpawn {
StoppableSpawn { handle }
}
pub fn cancel(&self) {
if let Some(handle) = self.handle.write().unwrap().take() {
handle.abort();
}
}
pub fn is_cancelled(&self) -> bool {
self.handle.read().unwrap().is_none()
}
}
pub struct UnreadNotificationsCount {
highlight_count: u32,
notification_count: u32,
}
impl UnreadNotificationsCount {
pub fn highlight_count(&self) -> u32 {
self.highlight_count
}
pub fn notification_count(&self) -> u32 {
self.notification_count
}
pub fn has_notifications(&self) -> bool {
self.notification_count != 0 || self.highlight_count != 0
}
}
impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
fn from(inner: RumaUnreadNotificationsCount) -> Self {
UnreadNotificationsCount {
highlight_count: inner
.highlight_count
.and_then(|x| x.try_into().ok())
.unwrap_or_default(),
notification_count: inner
.notification_count
.and_then(|x| x.try_into().ok())
.unwrap_or_default(),
}
}
}
pub struct SlidingSyncRoom {
inner: matrix_sdk::SlidingSyncRoom,
client: Client,
}
impl SlidingSyncRoom {
pub fn name(&self) -> Option<String> {
self.inner.name().map(ToOwned::to_owned)
}
pub fn room_id(&self) -> String {
self.inner.room_id().to_string()
}
pub fn is_dm(&self) -> Option<bool> {
self.inner.is_dm
}
pub fn is_initial(&self) -> Option<bool> {
self.inner.initial
}
pub fn is_loading_more(&self) -> bool {
self.inner.is_loading_more()
}
pub fn has_unread_notifications(&self) -> bool {
!self.inner.unread_notifications.is_empty()
}
pub fn unread_notifications(&self) -> Arc<UnreadNotificationsCount> {
Arc::new(self.inner.unread_notifications.clone().into())
}
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn latest_room_message(&self) -> Option<Arc<crate::messages::AnyMessage>> {
let messages = self.inner.timeline();
// room is having the latest events at the end,
let lock = messages.lock_ref();
for m in lock.iter().rev() {
if let Some(e) = crate::messages::sync_event_to_message(m.clone().into()) {
return Some(e);
}
}
None
}
pub fn full_room(&self) -> Option<Arc<super::Room>> {
self.client.get_room(self.inner.room_id()).map(|room| Arc::new(super::Room::new(room)))
}
}
pub struct UpdateSummary {
/// The views (according to their name), which have seen an update
pub views: Vec<String>,
pub rooms: Vec<String>,
}
pub struct RequiredState {
pub key: String,
pub value: String,
}
pub struct RoomSubscription {
pub required_state: Option<Vec<RequiredState>>,
pub timeline_limit: Option<u32>,
}
impl TryInto<RumaRoomSubscription> for RoomSubscription {
type Error = anyhow::Error;
fn try_into(self) -> anyhow::Result<RumaRoomSubscription> {
Ok(assign!(RumaRoomSubscription::default(), {
required_state: self.required_state.map(|r|
r.into_iter().map(|s| (s.key.into(), s.value)).collect()
).unwrap_or_default(),
timeline_limit: self.timeline_limit.map(|u| u.into())
}))
}
}
impl From<matrix_sdk::UpdateSummary> for UpdateSummary {
fn from(other: matrix_sdk::UpdateSummary) -> UpdateSummary {
UpdateSummary {
views: other.views,
rooms: other.rooms.into_iter().map(|r| r.as_str().to_owned()).collect(),
}
}
}
pub enum SlidingSyncViewRoomsListDiff {
Replace { values: Vec<RoomListEntry> },
InsertAt { index: u32, value: RoomListEntry },
UpdateAt { index: u32, value: RoomListEntry },
RemoveAt { index: u32 },
Move { old_index: u32, new_index: u32 },
Push { value: RoomListEntry },
}
impl From<VecDiff<MatrixRoomEntry>> for SlidingSyncViewRoomsListDiff {
fn from(other: VecDiff<MatrixRoomEntry>) -> Self {
match other {
VecDiff::Replace { values } => SlidingSyncViewRoomsListDiff::Replace {
values: values.into_iter().map(|e| (&e).into()).collect(),
},
VecDiff::InsertAt { index, value } => SlidingSyncViewRoomsListDiff::InsertAt {
index: index as u32,
value: (&value).into(),
},
VecDiff::UpdateAt { index, value } => SlidingSyncViewRoomsListDiff::UpdateAt {
index: index as u32,
value: (&value).into(),
},
VecDiff::RemoveAt { index } => {
SlidingSyncViewRoomsListDiff::RemoveAt { index: index as u32 }
}
VecDiff::Move { old_index, new_index } => SlidingSyncViewRoomsListDiff::Move {
old_index: old_index as u32,
new_index: new_index as u32,
},
VecDiff::Push { value } => {
SlidingSyncViewRoomsListDiff::Push { value: (&value).into() }
}
_ => unimplemented!("Clear and Pop aren't provided within sliding sync"),
}
}
}
#[derive(Clone, Debug)]
pub enum RoomListEntry {
Empty,
Invalidated { room_id: String },
Filled { room_id: String },
}
impl From<&MatrixRoomEntry> for RoomListEntry {
fn from(other: &MatrixRoomEntry) -> Self {
match other {
MatrixRoomEntry::Empty => RoomListEntry::Empty,
MatrixRoomEntry::Filled(b) => RoomListEntry::Filled { room_id: b.to_string() },
MatrixRoomEntry::Invalidated(b) => {
RoomListEntry::Invalidated { room_id: b.to_string() }
}
}
}
}
pub trait SlidingSyncViewRoomItemsObserver: Sync + Send {
fn did_receive_update(&self);
}
pub trait SlidingSyncViewRoomListObserver: Sync + Send {
fn did_receive_update(&self, diff: SlidingSyncViewRoomsListDiff);
}
pub trait SlidingSyncViewRoomsCountObserver: Sync + Send {
fn did_receive_update(&self, new_count: u32);
}
pub trait SlidingSyncViewStateObserver: Sync + Send {
fn did_receive_update(&self, new_state: SlidingSyncState);
}
#[derive(Clone, Default)]
pub struct SlidingSyncViewBuilder {
inner: matrix_sdk::SlidingSyncViewBuilder,
}
impl SlidingSyncViewBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn sync_mode(self: Arc<Self>, mode: SlidingSyncMode) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.sync_mode(mode);
Arc::new(builder)
}
pub fn sort(self: Arc<Self>, sort: Vec<String>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.sort(sort);
Arc::new(builder)
}
pub fn required_state(self: Arc<Self>, required_state: Vec<RequiredState>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder
.inner
.required_state(required_state.into_iter().map(|s| (s.key.into(), s.value)).collect());
Arc::new(builder)
}
pub fn batch_size(self: Arc<Self>, batch_size: u32) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.batch_size(batch_size);
Arc::new(builder)
}
pub fn timeline_limit(self: Arc<Self>, limit: u32) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.timeline_limit(limit);
Arc::new(builder)
}
pub fn no_timeline_limit(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.no_timeline_limit();
Arc::new(builder)
}
pub fn name(self: Arc<Self>, name: String) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.name(name);
Arc::new(builder)
}
pub fn ranges(self: Arc<Self>, ranges: Vec<(u32, u32)>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.ranges(ranges);
Arc::new(builder)
}
pub fn add_range(self: Arc<Self>, from: u32, to: u32) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.add_range(from, to);
Arc::new(builder)
}
pub fn reset_ranges(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.reset_ranges();
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSyncView>> {
let builder = unwrap_or_clone_arc(self);
Ok(Arc::new(builder.inner.build()?.into()))
}
}
#[derive(Clone)]
pub struct SlidingSyncView {
inner: matrix_sdk::SlidingSyncView,
}
impl From<matrix_sdk::SlidingSyncView> for SlidingSyncView {
fn from(inner: matrix_sdk::SlidingSyncView) -> Self {
SlidingSyncView { inner }
}
}
impl SlidingSyncView {
pub fn observe_state(
&self,
observer: Box<dyn SlidingSyncViewStateObserver>,
) -> Arc<StoppableSpawn> {
let mut signal = self.inner.state.signal_cloned().to_stream();
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
loop {
if let Some(new_state) = signal.next().await {
observer.did_receive_update(new_state);
}
}
})))
}
pub fn observe_room_list(
&self,
observer: Box<dyn SlidingSyncViewRoomListObserver>,
) -> Arc<StoppableSpawn> {
let mut room_list = self.inner.rooms_list.signal_vec_cloned().to_stream();
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
loop {
if let Some(diff) = room_list.next().await {
observer.did_receive_update(diff.into());
}
}
})))
}
pub fn observe_room_items(
&self,
observer: Box<dyn SlidingSyncViewRoomItemsObserver>,
) -> Arc<StoppableSpawn> {
let mut rooms_updated = self.inner.rooms_updated_broadcaster.signal_cloned().to_stream();
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
loop {
if rooms_updated.next().await.is_some() {
observer.did_receive_update();
}
}
})))
}
pub fn observe_rooms_count(
&self,
observer: Box<dyn SlidingSyncViewRoomsCountObserver>,
) -> Arc<StoppableSpawn> {
let mut rooms_count = self.inner.rooms_count.signal_cloned().to_stream();
Arc::new(StoppableSpawn::with_handle(RUNTIME.spawn(async move {
loop {
if let Some(Some(new)) = rooms_count.next().await {
observer.did_receive_update(new);
}
}
})))
}
/// Reset the ranges to a particular set
///
/// Remember to cancel the existing stream and fetch a new one as this will
/// only be applied on the next request.
pub fn set_range(&self, start: u32, end: u32) {
self.inner.set_range(start, end);
}
/// Set the ranges to fetch
///
/// Remember to cancel the existing stream and fetch a new one as this will
/// only be applied on the next request.
pub fn add_range(&self, start: u32, end: u32) {
self.inner.add_range(start, end);
}
pub fn reset_ranges(&self) {
self.inner.reset_ranges();
}
pub fn current_room_count(&self) -> Option<u32> {
self.inner.rooms_count.get_cloned()
}
pub fn current_rooms_list(&self) -> Vec<RoomListEntry> {
self.inner.rooms_list.lock_ref().as_slice().iter().map(|e| e.into()).collect()
}
}
pub trait SlidingSyncObserver: Sync + Send {
fn did_receive_sync_update(&self, summary: UpdateSummary);
}
pub struct SlidingSync {
inner: matrix_sdk::SlidingSync,
client: Client,
observer: Arc<RwLock<Option<Box<dyn SlidingSyncObserver>>>>,
sync_handle: Arc<RwLock<Option<JoinHandle<()>>>>,
}
impl SlidingSync {
fn new(inner: matrix_sdk::SlidingSync, client: Client) -> Self {
SlidingSync { inner, client, observer: Default::default(), sync_handle: Default::default() }
}
pub fn set_observer(&self, observer: Option<Box<dyn SlidingSyncObserver>>) {
*self.observer.write().unwrap() = observer;
}
pub fn subscribe(
&self,
room_id: String,
settings: Option<RoomSubscription>,
) -> anyhow::Result<()> {
let settings =
if let Some(settings) = settings { Some(settings.try_into()?) } else { None };
self.inner.subscribe(room_id.try_into()?, settings);
Ok(())
}
pub fn unsubscribe(&self, room_id: String) -> anyhow::Result<()> {
self.inner.unsubscribe(room_id.try_into()?);
Ok(())
}
#[allow(clippy::significant_drop_in_scrutinee)]
pub fn get_view(&self, name: String) -> Option<Arc<SlidingSyncView>> {
let views = self.inner.views.lock_ref();
for s in views.iter() {
if s.name == name {
return Some(Arc::new(SlidingSyncView { inner: s.clone() }));
}
}
None
}
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
Ok(self
.inner
.get_room(OwnedRoomId::try_from(room_id)?)
.map(|inner| Arc::new(SlidingSyncRoom { inner, client: self.client.clone() })))
}
pub fn get_rooms(
&self,
room_ids: Vec<String>,
) -> anyhow::Result<Vec<Option<Arc<SlidingSyncRoom>>>> {
let actual_ids = room_ids
.into_iter()
.map(OwnedRoomId::try_from)
.collect::<Result<Vec<OwnedRoomId>, IdParseError>>()?;
Ok(self
.inner
.get_rooms(actual_ids.into_iter())
.into_iter()
.map(|o| {
o.map(|inner| Arc::new(SlidingSyncRoom { inner, client: self.client.clone() }))
})
.collect())
}
pub fn sync(&self) -> Arc<StoppableSpawn> {
let inner = self.inner.clone();
let observer = self.observer.clone();
let spawn = Arc::new(StoppableSpawn::with_handle_ref(self.sync_handle.clone()));
let inner_spawn = spawn.clone();
{
let mut sync_handle = self.sync_handle.write().unwrap();
if let Some(handle) = sync_handle.take() {
handle.abort();
}
*sync_handle = Some(RUNTIME.spawn(async move {
let stream = inner.stream().await.unwrap();
pin_mut!(stream);
loop {
let update = match stream.next().await {
Some(Ok(u)) => u,
Some(Err(e)) => {
// FIXME: send this over the FFI
tracing::warn!("Sliding Sync failure: {:?}", e);
continue;
}
None => {
tracing::debug!("No update from loop, cancelled");
break;
}
};
if let Some(ref observer) = *observer.read().unwrap() {
observer.did_receive_sync_update(update.into());
} else {
// when the observer has been removed
// we cancel the loop
inner_spawn.cancel();
break;
}
}
}));
}
spawn
}
}
#[derive(Clone)]
pub struct SlidingSyncBuilder {
inner: MatrixSlidingSyncBuilder,
client: Client,
}
impl SlidingSyncBuilder {
pub fn homeserver(self: Arc<Self>, url: String) -> anyhow::Result<Arc<Self>> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.homeserver(url.parse()?);
Ok(Arc::new(builder))
}
pub fn add_fullsync_view(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.add_fullsync_view();
Arc::new(builder)
}
pub fn no_views(self: Arc<Self>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.no_views();
Arc::new(builder)
}
pub fn add_view(self: Arc<Self>, v: Arc<SlidingSyncView>) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
let view = unwrap_or_clone_arc(v);
builder.inner = builder.inner.add_view(view.inner);
Arc::new(builder)
}
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<SlidingSync>> {
let builder = unwrap_or_clone_arc(self);
Ok(Arc::new(SlidingSync::new(builder.inner.build()?, builder.client)))
}
}
impl Client {
pub fn full_sliding_sync(&self) -> anyhow::Result<Arc<SlidingSync>> {
RUNTIME.block_on(async move {
let builder = self.client.sliding_sync().await;
let inner = builder.add_fullsync_view().build()?;
Ok(Arc::new(SlidingSync::new(inner, self.clone())))
})
}
pub fn sliding_sync(&self) -> Arc<SlidingSyncBuilder> {
RUNTIME.block_on(async move {
let inner = self.client.sliding_sync().await;
Arc::new(SlidingSyncBuilder { inner, client: self.clone() })
})
}
}
+1 -2
View File
@@ -31,7 +31,6 @@ ignore:
- "bindings/matrix-sdk-crypto-nodejs"
- "bindings/matrix-sdk-ffi"
- "crates/matrix-sdk-indexeddb"
- "crates/matrix-sdk-test"
- "crates/matrix-sdk-test-macros"
- "testing"
- "labs"
- "xtask"

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