diff --git a/.github/workflows/prep-crypto-nodejs-release.yml b/.github/workflows/prep-crypto-nodejs-release.yml index d1865fdf6..2edf2cdfc 100644 --- a/.github/workflows/prep-crypto-nodejs-release.yml +++ b/.github/workflows/prep-crypto-nodejs-release.yml @@ -36,10 +36,13 @@ env: jobs: prepare-release: - name: "Package nodejs package" + name: "Preparing crypto-nodejs release tag" runs-on: ubuntu-latest + outputs: + tag: "${{ env.TAG_PREFIX }}${{ inputs.version }}" steps: - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 # Generate changelog since last tag, if given - name: Generate a changelog for upload @@ -65,16 +68,15 @@ jobs: - name: Set version id: package_version - uses: KageKirin/set-node-package-version@v0 - with: - version: ${{ inputs.version }} + working-directory: ${{ env.PKG_PATH }} + run: npm version ${{ inputs.version }} - uses: EndBug/add-and-commit@v9 with: default_author: github_actions message: "Tagging Crypto-Node.js for release" tag: "${{env.TAG_PREFIX}}${{ inputs.version }}" - new_banch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}" + new_branch: "gh-action/release-${{ env.TAG_PREFIX }}${{ inputs.version }}" push: true add: | ${{ env.PKG_PATH }}/package.json @@ -91,18 +93,25 @@ jobs: # no changes, use the default changelog for the body - name: Update Github Release notes - if: !inputs.previous_version + if: ${{!inputs.previous_version}} uses: softprops/action-gh-release@v1 with: draft: true tag_name: ${{ env.TAG_PREFIX }}${{ inputs.version }} body_path: "${{ env.PKG_PATH }}/CHANGELOG.md" - # 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 }}" 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}} diff --git a/.github/workflows/release-crypto-nodejs.yml b/.github/workflows/release-crypto-nodejs.yml index 6a65dbaf1..19e6fcd73 100644 --- a/.github/workflows/release-crypto-nodejs.yml +++ b/.github/workflows/release-crypto-nodejs.yml @@ -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: @@ -113,4 +134,6 @@ jobs: - name: Publish to npmjs.com uses: JS-DevTools/npm-publish@v1 with: + package: ${{env.PKG_PATH}}/package.json + access: public token: ${{ secrets.NPM_TOKEN }} diff --git a/CONVENTIONAL_COMMITS.md b/CONVENTIONAL_COMMITS.md new file mode 100644 index 000000000..c6a83fb1c --- /dev/null +++ b/CONVENTIONAL_COMMITS.md @@ -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: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
GroupScopeDefinition
CratessdkAbout the matrix-sdk crate.
appserviceAbout the matrix-sdk-appservice crate.
baseAbout the matrix-sdk-base crate.
commonAbout the matrix-sdk-common crate.
cryptoAbout the matrix-sdk-crypto crate.
indexeddbAbout the matrix-sdk-indexeddb crate.
qrcodeAbout the matrix-sdk-qrcode crate.
sledAbout the matrix-sdk-sled crate.
store-encryptionAbout the matrix-sdk-store-encryption crate.
testAbout the matrix-sdk-test and matrix-sdk-test-macros crate.
BindingsappleAbout the matrix-rust-components-swift binding.
crypto-nodejsAbout the matrix-sdk-crypto-nodejs binding.
crypto-jsAbout the matrix-sdk-crypto-js binding.
crypto-ffiAbout the matrix-sdk-crypto-ffi binding.
Labssled-state-inspectorAbout the sled-state-inspector project.
Continuous IntegrationxtaskAbout the xtask project.
+ +## 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. diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 8c9c7bcb0..9cd8c9fa1 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -12,7 +12,7 @@ criterion = { version = "0.3.5", features = ["async", "async_tokio", "html_repor 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 = "../testing/matrix-sdk-test", version = "0.5.0" } -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" } serde_json = "1.0.79" tempfile = "3.3.0" tokio = { version = "1.17.0", default-features = false, features = ["rt-multi-thread"] } diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index 60625dd03..ace17ec24 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -34,7 +34,7 @@ REL_TYPE_DIR="debug" # iOS Simulator arm64 if [ "$ACTIVE_ARCH" = "arm64" ]; then - cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim" + cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim" lipo -create \ "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ @@ -42,7 +42,7 @@ if [ "$ACTIVE_ARCH" = "arm64" ]; then # iOS Simulator intel else - cargo +nightly build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios" + cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios" lipo -create \ "${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ @@ -84,8 +84,6 @@ 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 diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index 445fcbbc5..3b200e82a 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -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 = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] } serde = "1.0.136" serde_json = "1.0.79" sha2 = "0.10.2" diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index 253619705..2b91685a9 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -30,7 +30,7 @@ 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 = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "rand", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] } wasm-bindgen = "0.2.80" wasm-bindgen-futures = "0.4.30" diff --git a/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md b/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md index fa95abc2a..2d83a8260 100644 --- a/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md +++ b/bindings/matrix-sdk-crypto-nodejs/CHANGELOG.md @@ -1,3 +1,32 @@ -# Matrix-Rust-SDK NodeJS Bindings +# Matrix-Rust-SDK Node.js Bindings --- Changelog Test -- \ No newline at end of file +## 0.1.0-beta.1 - 2022-07-14 + +- Fixing broken download link, [#842](https://github.com/matrix-org/matrix-rust-sdk/issues/842) + +## 0.1.0-beta.0 - 2022-07-12 + +Welcome to the first release of `matrix-sdk-crypto-nodejs`. This is a +Node.js binding for the Rust `matrix-sdk-crypto` library. This is a +no-network-IO implementation of a state machine, named `OlmMachine`, +that handles E2EE (End-to-End Encryption) for Matrix clients. + +The goal of this binding is _not_ to cover the entirety of the +`matrix-sdk-crypto` API, but only what's required to build Matrix bots +or Matrix bridges (i.e. to connect different networks together via the +Matrix protocol). + +This project replaces and deprecates a previous project, with the same +name and same goals, inside [the `matrix-rust-sdk-bindings` +repository](https://github.com/matrix-org/matrix-rust-sdk-bindings), +with the NPM package name `@turt2live/matrix-sdk-crypto-nodejs`. The +The new official package name is +`@matrix-org/matrix-sdk-crypto-nodejs`. + +Note: All bindings are now part of [the `matrix-rust-sdk` +repository](https://github.com/matrix-org/matrix-rust-sdk) (see the +`bindings/` root directory). + +[A documentation is available inside the new +`matrix-sdk-crypto-nodejs` +project](https://github.com/matrix-org/matrix-rust-sdk/tree/0bde5ccf38f8cda3865297a2d12ddcdaf4b80ca7/bindings/matrix-sdk-crypto-nodejs). diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 3814efa31..82a51626a 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -28,7 +28,7 @@ tracing = ["dep:tracing-subscriber"] 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 = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", 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" } diff --git a/bindings/matrix-sdk-crypto-nodejs/package.json b/bindings/matrix-sdk-crypto-nodejs/package.json index 3f8e56c4d..9699a89c7 100644 --- a/bindings/matrix-sdk-crypto-nodejs/package.json +++ b/bindings/matrix-sdk-crypto-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@matrix-org/matrix-sdk-crypto-nodejs", - "version": "0.1.0", + "version": "0.1.0-beta.1", "main": "index.js", "types": "index.d.ts", "napi": { diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index bc2f81bb1..a686272a9 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -31,6 +31,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" diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 80a24aa20..76d2ada9f 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -1,4 +1,6 @@ namespace sdk { + void setup_tracing(string configuration); + MediaSource media_source_from_url(string url); MessageEventContent message_event_content_from_markdown(string md); string gen_transaction_id(); @@ -156,24 +158,23 @@ interface MediaSource { [Error] enum AuthenticationError { - "ClientMissing", - "Generic", + "ClientMissing", + "Generic", +}; + +interface HomeserverLoginDetails { + string url(); + string? authentication_issuer(); + boolean supports_password_login(); }; interface AuthenticationService { constructor(string base_path); - [Throws=AuthenticationError] - string homeserver(); + HomeserverLoginDetails? homeserver_details(); [Throws=AuthenticationError] - string? authentication_issuer(); - - [Throws=AuthenticationError] - boolean supports_password_login(); - - [Throws=AuthenticationError] - void use_server(string server_name); + void configure_homeserver(string server_name); [Throws=AuthenticationError] Client login(string username, string password); diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 7ed148d82..0c3df8f12 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -1,12 +1,14 @@ use std::sync::Arc; +use futures_util::future::join3; use parking_lot::RwLock; -use super::{client::Client, client_builder::ClientBuilder}; +use super::{client::Client, client_builder::ClientBuilder, RUNTIME}; pub struct AuthenticationService { base_path: String, client: RwLock>>, + homeserver_details: RwLock>>, } #[derive(Debug, thiserror::Error)] @@ -23,52 +25,67 @@ impl From 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, + supports_password_login: bool, +} - /// The currently configured homeserver. - pub fn homeserver(&self) -> Result { - 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, AuthenticationError> { - self.client - .read() - .as_ref() - .ok_or(AuthenticationError::ClientMissing) - .map(|client| client.authentication_issuer()) + pub fn authentication_issuer(&self) -> Option { + self.authentication_issuer.clone() } /// Whether the current homeserver supports the password login flow. - pub fn supports_password_login(&self) -> Result { - 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> { + pub fn homeserver_details(&self) -> Option> { + self.homeserver_details.read().clone() + } + + /// Updates the service to authenticate with the homeserver for the + /// specified address. + pub fn configure_homeserver(&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)?; - *self.client.write() = Some(client); - Ok(()) + let mut builder = + Arc::new(ClientBuilder::new()).base_path(self.base_path.clone()).username(username); + + if server_name.starts_with("http://") || server_name.starts_with("https://") { + builder = builder.homeserver_url(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() = Some(client); + *self.homeserver_details.write() = Some(details); + + Ok(()) + }) } /// Performs a password login using the current homeserver. @@ -97,4 +114,23 @@ impl AuthenticationService { None => Err(AuthenticationError::ClientMissing), } } + + /// Get the homeserver login details from a client. + async fn details_from_client( + &self, + client: &Arc, + ) -> Result { + 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 }) + } } diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 0dda08d72..35538cb84 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -74,26 +74,27 @@ impl Client { /// The homeserver this client is configured to use. pub fn homeserver(&self) -> String { - RUNTIME.block_on(async move { self.client.homeserver().await.to_string() }) + RUNTIME.block_on(async move { self.async_homeserver().await }) + } + + 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 { - RUNTIME.block_on(async move { - self.client.authentication_issuer().await.map(|server| server.to_string()) - }) + pub async fn authentication_issuer(&self) -> Option { + 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 { - 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 { + 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) { diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 7fb23d268..db2461303 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -17,6 +17,7 @@ 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 = @@ -55,3 +56,10 @@ impl From for ClientError { ClientError::Generic { msg: e.to_string() } } } + +fn setup_tracing(configuration: String) { + tracing_subscriber::registry() + .with(EnvFilter::new(configuration)) + .with(fmt::layer().with_ansi(false)) + .init(); +} diff --git a/crates/matrix-sdk-appservice/Cargo.toml b/crates/matrix-sdk-appservice/Cargo.toml index ec1ccb4ff..90e569a60 100644 --- a/crates/matrix-sdk-appservice/Cargo.toml +++ b/crates/matrix-sdk-appservice/Cargo.toml @@ -34,7 +34,7 @@ http = "0.2.6" matrix-sdk = { version = "0.5.0", path = "../matrix-sdk", default-features = false, features = ["appservice"] } percent-encoding = "2.1.0" regex = "1.5.5" -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "appservice-api-s"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "appservice-api-s"] } serde = "1.0.136" serde_json = "1.0.79" serde_yaml = "0.8.23" diff --git a/crates/matrix-sdk-appservice/src/error.rs b/crates/matrix-sdk-appservice/src/error.rs index de3d15ed2..68a863ed5 100644 --- a/crates/matrix-sdk-appservice/src/error.rs +++ b/crates/matrix-sdk-appservice/src/error.rs @@ -41,41 +41,41 @@ pub enum Error { #[error("uri path is unknown")] UriPathUnknown, - #[error(transparent)] - HttpRequest(#[from] ruma::api::error::FromHttpRequestError), + #[error("HTTP request parsing error: {0}")] + FromHttpRequest(#[from] ruma::api::error::FromHttpRequestError), - #[error(transparent)] + #[error("identifier failed to parse: {0}")] Identifier(#[from] ruma::IdParseError), - #[error(transparent)] + #[error("HTTP error: {0}")] Http(#[from] http::Error), - #[error(transparent)] + #[error("url parse error: {0}")] Url(#[from] url::ParseError), - #[error(transparent)] + #[error("deserialization error: {0}")] Serde(#[from] serde::de::value::Error), - #[error(transparent)] + #[error("I/O error: {0}")] Io(#[from] std::io::Error), - #[error(transparent)] + #[error("http uri invalid error: {0}")] InvalidUri(#[from] http::uri::InvalidUri), #[error(transparent)] Matrix(#[from] matrix_sdk::Error), - #[error(transparent)] + #[error("regex error: {0}")] Regex(#[from] regex::Error), - #[error(transparent)] + #[error("serde yaml error: {0}")] SerdeYaml(#[from] serde_yaml::Error), - #[error(transparent)] + #[error("serde json error: {0}")] SerdeJson(#[from] serde_json::Error), - #[error(transparent)] - Utf8Error(#[from] std::str::Utf8Error), + #[error("utf8 error: {0}")] + Utf8(#[from] std::str::Utf8Error), #[error("warp rejection: {0}")] WarpRejection(String), diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index 10b612c2c..7fb2519c8 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -37,9 +37,6 @@ //! for the access tokens and because membership states for virtual users are //! determined based on the registered namespaces. //! -//! **Note:** Non-exclusive registration namespaces are not yet supported and -//! hence might lead to undefined behavior. -//! //! # Quickstart //! //! ```no_run @@ -95,7 +92,7 @@ use event_handler::AppserviceFn; pub use matrix_sdk; #[doc(no_inline)] pub use matrix_sdk::ruma; -use matrix_sdk::{bytes::Bytes, reqwest::Url, Client, ClientBuilder}; +use matrix_sdk::{reqwest::Url, Client, ClientBuilder}; use ruma::{ api::{ appservice::{ @@ -392,7 +389,7 @@ impl AppService { /// active virtual clients. /// /// [transaction]: https://spec.matrix.org/v1.2/application-service-api/#put_matrixappv1transactionstxnid - pub async fn receive_transaction( + async fn receive_transaction( &self, transaction: push_events::v1::IncomingRequest, ) -> Result<()> { @@ -515,66 +512,517 @@ impl AppService { } } -/// Ruma always expects the path to start with `/_matrix`, so we transform -/// accordingly. Handles [legacy routes] and appservice being located on a sub -/// path. -/// -/// [legacy routes]: https://matrix.org/docs/spec/application_service/r0.1.2#legacy-routes -// TODO: consider ruma PR -pub(crate) fn transform_request_path( - mut request: http::Request, -) -> Result> { - let uri = request.uri(); - // remove trailing slash from path - let path = uri.path().trim_end_matches('/').to_owned(); +#[cfg(test)] +mod tests { + use std::{ + future, + sync::{Arc, Mutex}, + }; - if !path.starts_with("/_matrix/app/v1/") { - let path = match path { - // special-case paths without value at the end - _ if path.ends_with("/_matrix/app/unstable/thirdparty/user") => { - "/_matrix/app/v1/thirdparty/user".to_owned() - } - _ if path.ends_with("/_matrix/app/unstable/thirdparty/location") => { - "/_matrix/app/v1/thirdparty/location".to_owned() - } - // regular paths with values at the end - _ => { - let mut path = path.split('/').into_iter().rev(); - let value = match path.next() { - Some(value) => value, - None => return Err(Error::UriEmptyPath), - }; + use matrix_sdk::{ + config::RequestConfig, + ruma::{api::appservice::Registration, events::room::member::OriginalSyncRoomMemberEvent}, + Client, + }; + use matrix_sdk_test::{appservice::TransactionBuilder, async_test, TimelineTestEvent}; + use ruma::{ + api::{appservice::event::push_events, MatrixVersion}, + events::AnyRoomEvent, + room_id, + serde::Raw, + }; + use serde_json::json; + use warp::{Filter, Reply}; + use wiremock::{ + matchers::{body_json, header, method, path}, + Mock, MockServer, ResponseTemplate, + }; - let mut path = match path.next() { - Some(path_segment) - if ["transactions", "users", "rooms"].contains(&path_segment) => - { - format!("/_matrix/app/v1/{}/{}", path_segment, value) - } - Some(path_segment) => match path.next() { - Some(path_segment2) if path_segment2 == "thirdparty" => { - format!("/_matrix/app/v1/thirdparty/{}/{}", path_segment, value) - } - _ => return Err(Error::UriPathUnknown), - }, - None => return Err(Error::UriEmptyPath), - }; + use super::*; - if let Some(query) = uri.query() { - path.push('?'); - path.push_str(query); - } - - path - } - }; - - let mut parts = uri.clone().into_parts(); - parts.path_and_query = Some(path.parse()?); - - let uri = parts.try_into().map_err(http::Error::from)?; - *request.uri_mut() = uri; + fn registration_string() -> String { + include_str!("../tests/registration.yaml").to_owned() } - Ok(request) + async fn appservice( + homeserver_url: Option, + registration: Option, + ) -> Result { + let _ = tracing_subscriber::fmt::try_init(); + + let registration = match registration { + Some(registration) => registration.into(), + None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(), + }; + + let homeserver_url = homeserver_url.unwrap_or_else(|| "http://localhost:1234".to_owned()); + let server_name = "localhost"; + + let client_builder = Client::builder() + .request_config(RequestConfig::default().disable_retry()) + .server_versions([MatrixVersion::V1_0]); + + AppService::with_client_builder( + homeserver_url.as_ref(), + server_name, + registration, + client_builder, + ) + .await + } + + #[async_test] + async fn test_register_virtual_user() -> Result<()> { + let server = MockServer::start().await; + let appservice = appservice(Some(server.uri()), None).await?; + + let localpart = "someone"; + Mock::given(method("POST")) + .and(path("/_matrix/client/r0/register")) + .and(header( + "authorization", + format!("Bearer {}", appservice.registration().as_token).as_str(), + )) + .and(body_json(json!({ + "username": localpart.to_owned(), + "type": "m.login.application_service" + }))) + .respond_with(ResponseTemplate::new(200).set_body_json(json!({ + "access_token": "abc123", + "device_id": "GHTYAJCE", + "user_id": format!("@{localpart}:localhost"), + }))) + .mount(&server) + .await; + + appservice.register_virtual_user(localpart, None).await?; + + Ok(()) + } + + #[async_test] + async fn test_put_transaction() -> Result<()> { + let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::Member); + let transaction = transaction_builder.build_json_transaction(); + + let appservice = appservice(None, None).await?; + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 200); + + Ok(()) + } + + #[async_test] + async fn test_put_transaction_with_repeating_txn_id() -> Result<()> { + let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::Member); + let transaction = transaction_builder.build_json_transaction(); + + let appservice = appservice(None, None).await?; + + #[allow(clippy::mutex_atomic)] + let on_state_member = Arc::new(Mutex::new(false)); + appservice + .virtual_user(None) + .await? + .register_event_handler({ + let on_state_member = on_state_member.clone(); + move |_ev: OriginalSyncRoomMemberEvent| { + *on_state_member.lock().unwrap() = true; + future::ready(()) + } + }) + .await; + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 200); + { + let on_room_member_called = *on_state_member.lock().unwrap(); + assert!(on_room_member_called); + } + + // Reset this to check that next time it doesnt get called + { + let mut on_room_member_called = on_state_member.lock().unwrap(); + *on_room_member_called = false; + } + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + // According to https://spec.matrix.org/v1.2/application-service-api/#pushing-events + // This should noop and return 200. + assert_eq!(status, 200); + { + let on_room_member_called = *on_state_member.lock().unwrap(); + // This time we should not have called the event handler. + assert!(!on_room_member_called); + } + + Ok(()) + } + + #[async_test] + async fn test_get_user() -> Result<()> { + let appservice = appservice(None, None).await?; + appservice.register_user_query(Box::new(|_, _| Box::pin(async move { true }))).await; + + let uri = "/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token"; + + let status = warp::test::request() + .method("GET") + .path(uri) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 200); + + Ok(()) + } + + #[async_test] + async fn test_get_room() -> Result<()> { + let appservice = appservice(None, None).await?; + appservice.register_room_query(Box::new(|_, _| Box::pin(async move { true }))).await; + + let uri = "/_matrix/app/v1/rooms/%23magicforest%3Aexample.com?access_token=hs_token"; + + let status = warp::test::request() + .method("GET") + .path(uri) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 200); + + Ok(()) + } + + #[async_test] + async fn test_invalid_access_token() -> Result<()> { + let uri = "/_matrix/app/v1/transactions/1?access_token=invalid_token"; + + let mut transaction_builder = TransactionBuilder::new(); + let transaction = + transaction_builder.add_room_event(TimelineTestEvent::Member).build_json_transaction(); + + let appservice = appservice(None, None).await?; + + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 401); + + Ok(()) + } + + #[async_test] + async fn test_no_access_token() -> Result<()> { + let uri = "/_matrix/app/v1/transactions/1"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::Member); + let transaction = transaction_builder.build_json_transaction(); + + let appservice = appservice(None, None).await?; + + { + let status = warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap() + .into_response() + .status(); + + assert_eq!(status, 401); + } + + Ok(()) + } + + #[async_test] + async fn test_event_handler() -> Result<()> { + let appservice = appservice(None, None).await?; + + #[allow(clippy::mutex_atomic)] + let on_state_member = Arc::new(Mutex::new(false)); + appservice + .virtual_user(None) + .await? + .register_event_handler({ + let on_state_member = on_state_member.clone(); + move |_ev: OriginalSyncRoomMemberEvent| { + *on_state_member.lock().unwrap() = true; + future::ready(()) + } + }) + .await; + + let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::Member); + let transaction = transaction_builder.build_json_transaction(); + + warp::test::request() + .method("PUT") + .path(uri) + .json(&transaction) + .filter(&appservice.warp_filter()) + .await + .unwrap(); + + let on_room_member_called = *on_state_member.lock().unwrap(); + assert!(on_room_member_called); + + Ok(()) + } + + #[async_test] + async fn test_unrelated_path() -> Result<()> { + let appservice = appservice(None, None).await?; + + let status = { + let consumer_filter = warp::any() + .and(appservice.warp_filter()) + .or(warp::get().and(warp::path("unrelated").map(warp::reply))); + + let response = warp::test::request() + .method("GET") + .path("/unrelated") + .filter(&consumer_filter) + .await? + .into_response(); + + response.status() + }; + + assert_eq!(status, 200); + + Ok(()) + } + + #[async_test] + async fn test_appservice_on_sub_path() -> Result<()> { + let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost"); + let uri_1 = "/sub_path/_matrix/app/v1/transactions/1?access_token=hs_token"; + let uri_2 = "/sub_path/_matrix/app/v1/transactions/2?access_token=hs_token"; + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::Member); + let transaction_1 = transaction_builder.build_json_transaction(); + + let mut transaction_builder = TransactionBuilder::new(); + transaction_builder.add_room_event(TimelineTestEvent::MemberNameChange); + let transaction_2 = transaction_builder.build_json_transaction(); + + let appservice = appservice(None, None).await?; + + { + warp::test::request() + .method("PUT") + .path(uri_1) + .json(&transaction_1) + .filter(&warp::path("sub_path").and(appservice.warp_filter())) + .await?; + + warp::test::request() + .method("PUT") + .path(uri_2) + .json(&transaction_2) + .filter(&warp::path("sub_path").and(appservice.warp_filter())) + .await?; + }; + + let members = appservice + .virtual_user(None) + .await? + .get_room(room_id) + .expect("Expected room to be available") + .members_no_sync() + .await?; + + assert_eq!(members[0].display_name().unwrap(), "changed"); + + Ok(()) + } + + #[async_test] + async fn test_receive_transaction() -> Result<()> { + tracing_subscriber::fmt().try_init().ok(); + let json = vec![ + Raw::new(&json!({ + "content": { + "avatar_url": null, + "displayname": "Appservice", + "membership": "join" + }, + "event_id": "$151800140479rdvjg:localhost", + "membership": "join", + "origin_server_ts": 151800140, + "sender": "@_appservice:localhost", + "state_key": "@_appservice:localhost", + "type": "m.room.member", + "room_id": "!coolplace:localhost", + "unsigned": { + "age": 2970366 + } + }))? + .cast::(), + Raw::new(&json!({ + "content": { + "avatar_url": null, + "displayname": "Appservice", + "membership": "join" + }, + "event_id": "$151800140491rfbja:localhost", + "membership": "join", + "origin_server_ts": 151800140, + "sender": "@_appservice:localhost", + "state_key": "@_appservice:localhost", + "type": "m.room.member", + "room_id": "!boringplace:localhost", + "unsigned": { + "age": 2970366 + } + }))? + .cast::(), + Raw::new(&json!({ + "content": { + "avatar_url": null, + "displayname": "Alice", + "membership": "join" + }, + "event_id": "$151800140517rfvjc:localhost", + "membership": "join", + "origin_server_ts": 151800140, + "sender": "@_appservice_alice:localhost", + "state_key": "@_appservice_alice:localhost", + "type": "m.room.member", + "room_id": "!coolplace:localhost", + "unsigned": { + "age": 2970366 + } + }))? + .cast::(), + Raw::new(&json!({ + "content": { + "avatar_url": null, + "displayname": "Bob", + "membership": "invite" + }, + "event_id": "$151800140594rfvjc:localhost", + "membership": "invite", + "origin_server_ts": 151800174, + "sender": "@_appservice_bob:localhost", + "state_key": "@_appservice_bob:localhost", + "type": "m.room.member", + "room_id": "!boringplace:localhost", + "unsigned": { + "age": 2970366 + } + }))? + .cast::(), + ]; + let appservice = appservice(None, None).await?; + + let alice = appservice.virtual_user(Some("_appservice_alice")).await?; + let bob = appservice.virtual_user(Some("_appservice_bob")).await?; + appservice + .receive_transaction(push_events::v1::IncomingRequest::new("dontcare".into(), json)) + .await?; + let coolplace = room_id!("!coolplace:localhost"); + let boringplace = room_id!("!boringplace:localhost"); + assert!( + alice.get_joined_room(coolplace).is_some(), + "Alice's membership in coolplace should be join" + ); + assert!( + bob.get_invited_room(boringplace).is_some(), + "Bob's membership in boringplace should be invite" + ); + assert!(alice.get_room(boringplace).is_none(), "Alice should not know about boringplace"); + assert!(bob.get_room(coolplace).is_none(), "Bob should not know about coolplace"); + Ok(()) + } + + mod registration { + use super::*; + + #[test] + fn test_registration() -> Result<()> { + let registration: Registration = serde_yaml::from_str(®istration_string())?; + let registration: AppServiceRegistration = registration.into(); + + assert_eq!(registration.id, "appservice"); + + Ok(()) + } + + #[test] + fn test_registration_from_yaml_file() -> Result<()> { + let registration = + AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?; + + assert_eq!(registration.id, "appservice"); + + Ok(()) + } + + #[test] + fn test_registration_from_yaml_str() -> Result<()> { + let registration = AppServiceRegistration::try_from_yaml_str(registration_string())?; + + assert_eq!(registration.id, "appservice"); + + Ok(()) + } + } } diff --git a/crates/matrix-sdk-appservice/src/webserver.rs b/crates/matrix-sdk-appservice/src/webserver.rs index e649b6f53..9e29a08a9 100644 --- a/crates/matrix-sdk-appservice/src/webserver.rs +++ b/crates/matrix-sdk-appservice/src/webserver.rs @@ -107,10 +107,11 @@ mod filters { warp::any() .and(valid_access_token(appservice.registration().hs_token.clone())) .map(move || appservice.clone()) - .and(http_request().and_then(|request| async move { - let request = crate::transform_request_path(request).map_err(Error::from)?; - Ok::, Rejection>(request) - })) + .and( + http_request().and_then(|request| async move { + Ok::, Rejection>(request) + }), + ) .boxed() } diff --git a/crates/matrix-sdk-appservice/tests/tests.rs b/crates/matrix-sdk-appservice/tests/tests.rs deleted file mode 100644 index fbebc9670..000000000 --- a/crates/matrix-sdk-appservice/tests/tests.rs +++ /dev/null @@ -1,513 +0,0 @@ -use std::{ - future, - sync::{Arc, Mutex}, -}; - -use matrix_sdk::{ - config::RequestConfig, - ruma::{api::appservice::Registration, events::room::member::OriginalSyncRoomMemberEvent}, - Client, -}; -use matrix_sdk_appservice::*; -use matrix_sdk_test::{appservice::TransactionBuilder, async_test, test_json, TimelineTestEvent}; -use ruma::{ - api::{appservice::event::push_events, MatrixVersion}, - events::AnyRoomEvent, - room_id, - serde::Raw, -}; -use serde_json::json; -use warp::{Filter, Reply}; -use wiremock::{ - matchers::{body_json, header, method, path}, - Mock, MockServer, ResponseTemplate, -}; - -fn registration_string() -> String { - include_str!("../tests/registration.yaml").to_owned() -} - -async fn appservice( - homeserver_url: Option, - registration: Option, -) -> Result { - // env::set_var( - // "RUST_LOG", - // "wiremock=debug,matrix_sdk=debug,ruma=debug,warp=debug", - // ); - let _ = tracing_subscriber::fmt::try_init(); - - let registration = match registration { - Some(registration) => registration.into(), - None => AppServiceRegistration::try_from_yaml_str(registration_string()).unwrap(), - }; - - let homeserver_url = homeserver_url.unwrap_or_else(|| "http://localhost:1234".to_owned()); - let server_name = "localhost"; - - let client_builder = Client::builder() - .request_config(RequestConfig::default().disable_retry()) - .server_versions([MatrixVersion::V1_0]); - - AppService::with_client_builder( - homeserver_url.as_ref(), - server_name, - registration, - client_builder, - ) - .await -} - -#[async_test] -async fn test_register_virtual_user() -> Result<()> { - let server = MockServer::start().await; - let appservice = appservice(Some(server.uri()), None).await?; - - let localpart = "someone"; - Mock::given(method("POST")) - .and(path("/_matrix/client/r0/register")) - .and(header( - "authorization", - format!("Bearer {}", appservice.registration().as_token).as_str(), - )) - .and(body_json(json!({ - "username": localpart.to_owned(), - "type": "m.login.application_service" - }))) - .respond_with(ResponseTemplate::new(200).set_body_json(json!({ - "access_token": "abc123", - "device_id": "GHTYAJCE", - "user_id": format!("@{localpart}:localhost"), - }))) - .mount(&server) - .await; - - appservice.register_virtual_user(localpart, None).await?; - - Ok(()) -} - -#[async_test] -async fn test_put_transaction() -> Result<()> { - let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::Member); - let transaction = transaction_builder.build_json_transaction(); - - let appservice = appservice(None, None).await?; - - let status = warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 200); - - Ok(()) -} - -#[async_test] -async fn test_put_transaction_with_repeating_txn_id() -> Result<()> { - let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::Member); - let transaction = transaction_builder.build_json_transaction(); - - let appservice = appservice(None, None).await?; - - #[allow(clippy::mutex_atomic)] - let on_state_member = Arc::new(Mutex::new(false)); - appservice - .virtual_user(None) - .await? - .register_event_handler({ - let on_state_member = on_state_member.clone(); - move |_ev: OriginalSyncRoomMemberEvent| { - *on_state_member.lock().unwrap() = true; - future::ready(()) - } - }) - .await; - - let status = warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 200); - { - let on_room_member_called = *on_state_member.lock().unwrap(); - assert!(on_room_member_called); - } - - // Reset this to check that next time it doesnt get called - { - let mut on_room_member_called = on_state_member.lock().unwrap(); - *on_room_member_called = false; - } - - let status = warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - // According to https://spec.matrix.org/v1.2/application-service-api/#pushing-events - // This should noop and return 200. - assert_eq!(status, 200); - { - let on_room_member_called = *on_state_member.lock().unwrap(); - // This time we should not have called the event handler. - assert!(!on_room_member_called); - } - - Ok(()) -} - -#[async_test] -async fn test_get_user() -> Result<()> { - let appservice = appservice(None, None).await?; - appservice.register_user_query(Box::new(|_, _| Box::pin(async move { true }))).await; - - let uri = "/_matrix/app/v1/users/%40_botty_1%3Adev.famedly.local?access_token=hs_token"; - - let status = warp::test::request() - .method("GET") - .path(uri) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 200); - - Ok(()) -} - -#[async_test] -async fn test_get_room() -> Result<()> { - let appservice = appservice(None, None).await?; - appservice.register_room_query(Box::new(|_, _| Box::pin(async move { true }))).await; - - let uri = "/_matrix/app/v1/rooms/%23magicforest%3Aexample.com?access_token=hs_token"; - - let status = warp::test::request() - .method("GET") - .path(uri) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 200); - - Ok(()) -} - -#[async_test] -async fn test_invalid_access_token() -> Result<()> { - let uri = "/_matrix/app/v1/transactions/1?access_token=invalid_token"; - - let mut transaction_builder = TransactionBuilder::new(); - let transaction = - transaction_builder.add_room_event(TimelineTestEvent::Member).build_json_transaction(); - - let appservice = appservice(None, None).await?; - - let status = warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 401); - - Ok(()) -} - -#[async_test] -async fn test_no_access_token() -> Result<()> { - let uri = "/_matrix/app/v1/transactions/1"; - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::Member); - let transaction = transaction_builder.build_json_transaction(); - - let appservice = appservice(None, None).await?; - - { - let status = warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap() - .into_response() - .status(); - - assert_eq!(status, 401); - } - - Ok(()) -} - -#[async_test] -async fn test_event_handler() -> Result<()> { - let appservice = appservice(None, None).await?; - - #[allow(clippy::mutex_atomic)] - let on_state_member = Arc::new(Mutex::new(false)); - appservice - .virtual_user(None) - .await? - .register_event_handler({ - let on_state_member = on_state_member.clone(); - move |_ev: OriginalSyncRoomMemberEvent| { - *on_state_member.lock().unwrap() = true; - future::ready(()) - } - }) - .await; - - let uri = "/_matrix/app/v1/transactions/1?access_token=hs_token"; - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::Member); - let transaction = transaction_builder.build_json_transaction(); - - warp::test::request() - .method("PUT") - .path(uri) - .json(&transaction) - .filter(&appservice.warp_filter()) - .await - .unwrap(); - - let on_room_member_called = *on_state_member.lock().unwrap(); - assert!(on_room_member_called); - - Ok(()) -} - -#[async_test] -async fn test_unrelated_path() -> Result<()> { - let appservice = appservice(None, None).await?; - - let status = { - let consumer_filter = warp::any() - .and(appservice.warp_filter()) - .or(warp::get().and(warp::path("unrelated").map(warp::reply))); - - let response = warp::test::request() - .method("GET") - .path("/unrelated") - .filter(&consumer_filter) - .await? - .into_response(); - - response.status() - }; - - assert_eq!(status, 200); - - Ok(()) -} - -#[async_test] -async fn test_appservice_on_sub_path() -> Result<()> { - let room_id = &test_json::DEFAULT_SYNC_ROOM_ID; - let uri_1 = "/sub_path/_matrix/app/v1/transactions/1?access_token=hs_token"; - let uri_2 = "/sub_path/_matrix/app/v1/transactions/2?access_token=hs_token"; - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::Member); - let transaction_1 = transaction_builder.build_json_transaction(); - - let mut transaction_builder = TransactionBuilder::new(); - transaction_builder.add_room_event(TimelineTestEvent::MemberNameChange); - let transaction_2 = transaction_builder.build_json_transaction(); - - let appservice = appservice(None, None).await?; - - { - warp::test::request() - .method("PUT") - .path(uri_1) - .json(&transaction_1) - .filter(&warp::path("sub_path").and(appservice.warp_filter())) - .await?; - - warp::test::request() - .method("PUT") - .path(uri_2) - .json(&transaction_2) - .filter(&warp::path("sub_path").and(appservice.warp_filter())) - .await?; - }; - - let members = appservice - .virtual_user(None) - .await? - .get_room(room_id) - .expect("Expected room to be available") - .members_no_sync() - .await?; - - assert_eq!(members[0].display_name().unwrap(), "changed"); - - Ok(()) -} - -#[async_test] -async fn test_receive_transaction() -> Result<()> { - tracing_subscriber::fmt().try_init().ok(); - let json = vec![ - Raw::new(&json!({ - "content": { - "avatar_url": null, - "displayname": "Appservice", - "membership": "join" - }, - "event_id": "$151800140479rdvjg:localhost", - "membership": "join", - "origin_server_ts": 151800140, - "sender": "@_appservice:localhost", - "state_key": "@_appservice:localhost", - "type": "m.room.member", - "room_id": "!coolplace:localhost", - "unsigned": { - "age": 2970366 - } - }))? - .cast::(), - Raw::new(&json!({ - "content": { - "avatar_url": null, - "displayname": "Appservice", - "membership": "join" - }, - "event_id": "$151800140491rfbja:localhost", - "membership": "join", - "origin_server_ts": 151800140, - "sender": "@_appservice:localhost", - "state_key": "@_appservice:localhost", - "type": "m.room.member", - "room_id": "!boringplace:localhost", - "unsigned": { - "age": 2970366 - } - }))? - .cast::(), - Raw::new(&json!({ - "content": { - "avatar_url": null, - "displayname": "Alice", - "membership": "join" - }, - "event_id": "$151800140517rfvjc:localhost", - "membership": "join", - "origin_server_ts": 151800140, - "sender": "@_appservice_alice:localhost", - "state_key": "@_appservice_alice:localhost", - "type": "m.room.member", - "room_id": "!coolplace:localhost", - "unsigned": { - "age": 2970366 - } - }))? - .cast::(), - Raw::new(&json!({ - "content": { - "avatar_url": null, - "displayname": "Bob", - "membership": "invite" - }, - "event_id": "$151800140594rfvjc:localhost", - "membership": "invite", - "origin_server_ts": 151800174, - "sender": "@_appservice_bob:localhost", - "state_key": "@_appservice_bob:localhost", - "type": "m.room.member", - "room_id": "!boringplace:localhost", - "unsigned": { - "age": 2970366 - } - }))? - .cast::(), - ]; - let appservice = appservice(None, None).await?; - - let alice = appservice.virtual_user(Some("_appservice_alice")).await?; - let bob = appservice.virtual_user(Some("_appservice_bob")).await?; - appservice - .receive_transaction(push_events::v1::IncomingRequest::new("dontcare".into(), json)) - .await?; - let coolplace = room_id!("!coolplace:localhost"); - let boringplace = room_id!("!boringplace:localhost"); - assert!( - alice.get_joined_room(coolplace).is_some(), - "Alice's membership in coolplace should be join" - ); - assert!( - bob.get_invited_room(boringplace).is_some(), - "Bob's membership in boringplace should be invite" - ); - assert!(alice.get_room(boringplace).is_none(), "Alice should not know about boringplace"); - assert!(bob.get_room(coolplace).is_none(), "Bob should not know about coolplace"); - Ok(()) -} - -mod registration { - use super::*; - - #[test] - fn test_registration() -> Result<()> { - let registration: Registration = serde_yaml::from_str(®istration_string())?; - let registration: AppServiceRegistration = registration.into(); - - assert_eq!(registration.id, "appservice"); - - Ok(()) - } - - #[test] - fn test_registration_from_yaml_file() -> Result<()> { - let registration = AppServiceRegistration::try_from_yaml_file("./tests/registration.yaml")?; - - assert_eq!(registration.id, "appservice"); - - Ok(()) - } - - #[test] - fn test_registration_from_yaml_str() -> Result<()> { - let registration = AppServiceRegistration::try_from_yaml_str(registration_string())?; - - assert_eq!(registration.id, "appservice"); - - Ok(()) - } -} diff --git a/crates/matrix-sdk-base/Cargo.toml b/crates/matrix-sdk-base/Cargo.toml index 8e7fd1ae1..6af59b9a1 100644 --- a/crates/matrix-sdk-base/Cargo.toml +++ b/crates/matrix-sdk-base/Cargo.toml @@ -43,10 +43,10 @@ tracing = "0.1.34" zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "js", "canonical-json"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "canonical-json"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "canonical-json"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "canonical-json"] } [dev-dependencies] futures = { version = "0.3.21", default-features = false, features = ["executor"] } diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index e7efca697..5d58c040a 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -66,6 +66,7 @@ use crate::{ store::{ ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, Store, StoreConfig, }, + StateStore, }; /// A no IO Client implementation. @@ -135,9 +136,20 @@ impl BaseClient { self.store.session() } + /// Get all the rooms this client knows about. + pub fn get_rooms(&self) -> Vec { + self.store.get_rooms() + } + + /// Get all the rooms this client knows about. + pub fn get_stripped_rooms(&self) -> Vec { + self.store.get_stripped_rooms() + } + /// Get a reference to the store. - pub fn store(&self) -> &Store { - &self.store + #[allow(unknown_lints, clippy::explicit_auto_deref)] + pub fn store(&self) -> &dyn StateStore { + &*self.store } /// Is the client logged in. diff --git a/crates/matrix-sdk-base/src/lib.rs b/crates/matrix-sdk-base/src/lib.rs index 3924d80b2..b7bbaeecb 100644 --- a/crates/matrix-sdk-base/src/lib.rs +++ b/crates/matrix-sdk-base/src/lib.rs @@ -43,7 +43,7 @@ pub use http; pub use matrix_sdk_crypto as crypto; pub use once_cell; pub use rooms::{DisplayName, Room, RoomInfo, RoomMember, RoomType}; -pub use store::{StateChanges, StateStore, Store, StoreError}; +pub use store::{StateChanges, StateStore, StoreError}; pub use utils::{ MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent, RedactedMinimalStateEvent, }; diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 39fe79c11..fe286f965 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -103,16 +103,7 @@ impl Room { room_id: &RoomId, room_type: RoomType, ) -> Self { - let room_info = RoomInfo { - room_id: room_id.into(), - room_type, - notification_counts: Default::default(), - summary: Default::default(), - members_synced: false, - last_prev_batch: None, - base_info: BaseRoomInfo::new(), - }; - + let room_info = RoomInfo::new(room_id, room_type); Self::restore(own_user_id, store, room_info) } @@ -655,6 +646,19 @@ pub struct RoomInfo { } impl RoomInfo { + #[doc(hidden)] // used by store tests, otherwise it would be pub(crate) + pub fn new(room_id: &RoomId, room_type: RoomType) -> Self { + Self { + room_id: room_id.into(), + room_type, + notification_counts: Default::default(), + summary: Default::default(), + members_synced: false, + last_prev_batch: None, + base_info: BaseRoomInfo::new(), + } + } + /// Mark this Room as joined pub fn mark_as_joined(&mut self) { self.room_type = RoomType::Joined; diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index cf827374d..0af39b2b8 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -52,7 +52,7 @@ macro_rules! statestore_integration_tests { use matrix_sdk_test::{async_test, test_json}; use ruma::{ api::client::media::get_content_thumbnail::v3::Method, - device_id, event_id, + event_id, events::{ presence::PresenceEvent, receipt::ReceiptType, @@ -83,14 +83,13 @@ macro_rules! statestore_integration_tests { deserialized_responses::{ RoomEvent, SyncRoomEvent, TimelineSlice}, }; use $crate::{ - RoomType, Session, media::{MediaFormat, MediaRequest, MediaThumbnailSize}, store::{ - Store, StateStore, Result as StoreResult, StateChanges - } + }, + RoomInfo, RoomType, }; use super::get_store; @@ -115,22 +114,13 @@ macro_rules! statestore_integration_tests { } /// Populate the given `StateStore`. - pub(crate) async fn populated_store(inner: Arc) -> StoreResult { + pub async fn populate_store(store: Arc) -> StoreResult<()> { let mut changes = StateChanges::default(); - let store = Store::new(inner); let user_id = user_id(); let invited_user_id = invited_user_id(); let room_id = room_id(); let stripped_room_id = stripped_room_id(); - let device_id = device_id!("device"); - - let session = Session { - access_token: "token".to_owned(), - user_id: user_id.to_owned(), - device_id: device_id.to_owned(), - }; - store.restore_session(session).await.unwrap(); changes.sync_token = Some("t392-516_47314_0_7_1_1_1_11444_1".to_owned()); @@ -147,7 +137,7 @@ macro_rules! statestore_integration_tests { let pushrules_event = pushrules_raw.deserialize().unwrap(); changes.add_account_data(pushrules_event, pushrules_raw); - let mut room = store.get_or_create_room(room_id, RoomType::Joined).await.clone_info(); + let mut room = RoomInfo::new(room_id, RoomType::Joined); room.mark_as_left(); let tag_json: &JsonValue = &test_json::TAG; @@ -220,8 +210,7 @@ macro_rules! statestore_integration_tests { changes.members.insert(room_id.to_owned(), room_members); changes.add_room(room); - let mut stripped_room = - store.get_or_create_stripped_room(stripped_room_id).await.clone_info(); + let mut stripped_room = RoomInfo::new(stripped_room_id, RoomType::Invited); let stripped_name_json: &JsonValue = &test_json::NAME_STRIPPED; let stripped_name_raw = @@ -249,7 +238,7 @@ macro_rules! statestore_integration_tests { changes.add_stripped_member(stripped_room_id, stripped_member_event); store.save_changes(&changes).await?; - Ok(store) + Ok(()) } fn power_level_event() -> Raw { @@ -305,11 +294,12 @@ macro_rules! statestore_integration_tests { let user_id = user_id(); let inner_store = get_store().await?; - let store = populated_store(Arc::new(inner_store)).await?; + let store = Arc::new(inner_store); + populate_store(store.clone()).await?; assert!(store.get_sync_token().await?.is_some()); assert!(store.get_presence_event(user_id).await?.is_some()); - assert_eq!(store.get_room_infos().await?.len(), 1, "Expected to find 1 room info "); + assert_eq!(store.get_room_infos().await?.len(), 1, "Expected to find 1 room info"); assert_eq!(store.get_stripped_room_infos().await?.len(), 1, "Expected to find 1 stripped room info"); assert!(store.get_account_data_event(GlobalAccountDataEventType::PushRules).await?.is_some()); @@ -580,25 +570,24 @@ macro_rules! statestore_integration_tests { #[async_test] async fn test_persist_invited_room() -> StoreResult<()> { - let stripped_room_id = stripped_room_id(); let inner_store = get_store().await?; - let store = populated_store(Arc::new(inner_store)).await?; + let store = Arc::new(inner_store); + populate_store(store.clone()).await?; assert_eq!(store.get_stripped_room_infos().await?.len(), 1); - assert!(store.get_stripped_room(stripped_room_id).is_some()); - // populate rooom Ok(()) } #[async_test] - async fn test_room_removal() -> StoreResult<()> { + async fn test_room_removal() -> StoreResult<()> { let room_id = room_id(); let user_id = user_id(); let inner_store = get_store().await?; let stripped_room_id = stripped_room_id(); - let store = populated_store(Arc::new(inner_store)).await?; + let store = Arc::new(inner_store); + populate_store(store.clone()).await?; store.remove_room(room_id).await?; diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index acc0992f0..a135d782c 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -407,7 +407,7 @@ where /// This adds additional higher level store functionality on top of a /// `StateStore` implementation. #[derive(Debug, Clone)] -pub struct Store { +pub(crate) struct Store { pub(super) inner: Arc, session: Arc>, /// The current sync token that should be used for the next sync call. diff --git a/crates/matrix-sdk-common/Cargo.toml b/crates/matrix-sdk-common/Cargo.toml index 5256a7319..8c8d49de0 100644 --- a/crates/matrix-sdk-common/Cargo.toml +++ b/crates/matrix-sdk-common/Cargo.toml @@ -16,7 +16,7 @@ default-target = "x86_64-unknown-linux-gnu" targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [dependencies] -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] } serde = "1.0.136" [target.'cfg(target_arch = "wasm32")'.dependencies] diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 6e3be638c..e97269045 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -51,11 +51,11 @@ zeroize = { version = "1.3.0", features = ["zeroize_derive"] } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1.18", default-features = false, features = ["time"] } -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd" } [target.'cfg(target_arch = "wasm32")'.dependencies] -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c", "js", "rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } vodozemac = { git = "https://github.com/matrix-org/vodozemac/", rev = "2404f83f7d3a3779c1f518e4d949f7da9677c3dd", features = ["js"] } [dev-dependencies] diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index fd1450e16..ceb248a67 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -1098,12 +1098,10 @@ impl RequestState { // before the other side tried to do the same; ignore it if we did and // we're the lexicographically smaller user ID (or device ID if equal). use std::cmp::Ordering; - match (sender.cmp(own_user_id), device.device_id().cmp(own_device_id)) { - (Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater) => { - false - } - _ => true, - } + !matches!( + (sender.cmp(own_user_id), device.device_id().cmp(own_device_id)), + (Ordering::Greater, _) | (Ordering::Equal, Ordering::Greater) + ) } else { true }; diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index dd5df6e61..79e659f95 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -29,7 +29,7 @@ indexed_db_futures = "0.2.3" matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base" } matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true } matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" } -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" } serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 552da61bd..4a05c2e75 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -25,7 +25,7 @@ byteorder = "1.4.3" image = { version = "0.23.0", optional = true } qrcode = { version = "0.12.0", default-features = false } rqrr = { version = "0.4.0", optional = true } -ruma-common = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" } +ruma-common = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" } thiserror = "1.0.30" [dependencies.vodozemac] diff --git a/crates/matrix-sdk-sled/Cargo.toml b/crates/matrix-sdk-sled/Cargo.toml index 01c33ee39..357479128 100644 --- a/crates/matrix-sdk-sled/Cargo.toml +++ b/crates/matrix-sdk-sled/Cargo.toml @@ -37,7 +37,7 @@ matrix-sdk-base = { version = "0.5.0", path = "../matrix-sdk-base", optional = t matrix-sdk-common = { version = "0.5.0", path = "../matrix-sdk-common" } matrix-sdk-crypto = { version = "0.5.0", path = "../matrix-sdk-crypto", optional = true } matrix-sdk-store-encryption = { version = "0.1.0", path = "../matrix-sdk-store-encryption" } -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" } serde = "1.0.136" serde_json = "1.0.79" sled = "0.34.7" diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 0dac9af53..9da698a17 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -110,13 +110,8 @@ default_features = false [dependencies.ruma] git = "https://github.com/ruma/ruma" -rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" -features = ["client-api-c", "compat", "rand", "unstable-msc2448"] - -[dependencies.ruma-client-api] -git = "https://github.com/ruma/ruma" -rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" -features = ["unstable-msc2965"] +rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" +features = ["client-api-c", "compat", "rand", "unstable-msc2448", "unstable-msc2965"] [dependencies.tokio-stream] version = "0.1.8" diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index b3776d9bf..61840003c 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -30,7 +30,7 @@ use futures_core::stream::Stream; use matrix_sdk_base::{ deserialized_responses::SyncResponse, media::{MediaEventContent, MediaFormat, MediaRequest, MediaThumbnailSize}, - BaseClient, Session, Store, + BaseClient, Session, StateStore, }; use matrix_sdk_common::{ instant::{Duration, Instant}, @@ -320,12 +320,12 @@ impl Client { /// Can be used with [`Client::restore_login`] to restore a previously /// logged-in session. pub fn session(&self) -> Option<&Session> { - self.store().session() + self.base_client().session() } - /// Get a reference to the store. - pub fn store(&self) -> &Store { - self.inner.base_client.store() + /// Get a reference to the state store. + pub fn store(&self) -> &dyn StateStore { + self.base_client().store() } /// Get the account of the current owner of the client. @@ -564,7 +564,7 @@ impl Client { /// /// This will return the list of joined, invited, and left rooms. pub fn rooms(&self) -> Vec { - self.store() + self.base_client() .get_rooms() .into_iter() .map(|room| room::Common::new(self.clone(), room).into()) @@ -573,7 +573,7 @@ impl Client { /// Returns the joined rooms this client knows about. pub fn joined_rooms(&self) -> Vec { - self.store() + self.base_client() .get_rooms() .into_iter() .filter_map(|room| room::Joined::new(self.clone(), room)) @@ -582,7 +582,7 @@ impl Client { /// Returns the invited rooms this client knows about. pub fn invited_rooms(&self) -> Vec { - self.store() + self.base_client() .get_stripped_rooms() .into_iter() .filter_map(|room| room::Invited::new(self.clone(), room)) @@ -591,7 +591,7 @@ impl Client { /// Returns the left rooms this client knows about. pub fn left_rooms(&self) -> Vec { - self.store() + self.base_client() .get_rooms() .into_iter() .filter_map(|room| room::Left::new(self.clone(), room)) @@ -604,7 +604,9 @@ impl Client { /// /// `room_id` - The unique id of the room that should be fetched. pub fn get_room(&self, room_id: &RoomId) -> Option { - self.store().get_room(room_id).map(|room| room::Common::new(self.clone(), room).into()) + self.base_client() + .get_room(room_id) + .map(|room| room::Common::new(self.clone(), room).into()) } /// Get a joined room with the given room id. @@ -613,7 +615,7 @@ impl Client { /// /// `room_id` - The unique id of the room that should be fetched. pub fn get_joined_room(&self, room_id: &RoomId) -> Option { - self.store().get_room(room_id).and_then(|room| room::Joined::new(self.clone(), room)) + self.base_client().get_room(room_id).and_then(|room| room::Joined::new(self.clone(), room)) } /// Get an invited room with the given room id. @@ -622,7 +624,7 @@ impl Client { /// /// `room_id` - The unique id of the room that should be fetched. pub fn get_invited_room(&self, room_id: &RoomId) -> Option { - self.store().get_room(room_id).and_then(|room| room::Invited::new(self.clone(), room)) + self.base_client().get_room(room_id).and_then(|room| room::Invited::new(self.clone(), room)) } /// Get a left room with the given room id. @@ -631,7 +633,7 @@ impl Client { /// /// `room_id` - The unique id of the room that should be fetched. pub fn get_left_room(&self, room_id: &RoomId) -> Option { - self.store().get_room(room_id).and_then(|room| room::Left::new(self.clone(), room)) + self.base_client().get_room(room_id).and_then(|room| room::Left::new(self.clone(), room)) } /// Resolve a room alias to a room id and a list of servers which know diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 8be144f20..9e740a9e2 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -176,13 +176,13 @@ impl Common { /// # let homeserver = Url::parse("http://example.com").unwrap(); /// # use futures::executor::block_on; /// # block_on(async { - /// let request = MessagesOptions::backward("t47429-4392820_219380_26003_2265"); + /// let options = MessagesOptions::backward().from("t47429-4392820_219380_26003_2265"); /// /// let mut client = Client::new(homeserver).await.unwrap(); /// let room = client /// .get_joined_room(room_id!("!roomid:example.com")) /// .unwrap(); - /// assert!(room.messages(request).await.is_ok()); + /// assert!(room.messages(options).await.is_ok()); /// # }); /// ``` pub async fn messages(&self, options: MessagesOptions<'_>) -> Result { @@ -264,7 +264,7 @@ impl Common { /// # Examples /// ```no_run /// # use std::convert::TryFrom; - /// use matrix_sdk::{room::MessagesOptions, Client}; + /// use matrix_sdk::Client; /// # use matrix_sdk::ruma::{ /// # api::client::filter::RoomEventFilter, /// # room_id, @@ -344,7 +344,7 @@ impl Common { /// # Examples /// ```no_run /// # use std::convert::TryFrom; - /// use matrix_sdk::{room::MessagesOptions, Client}; + /// use matrix_sdk::Client; /// # use matrix_sdk::ruma::{ /// # api::client::filter::RoomEventFilter, /// # room_id, @@ -403,7 +403,7 @@ impl Common { /// # Examples /// ```no_run /// # use std::convert::TryFrom; - /// use matrix_sdk::{room::MessagesOptions, Client}; + /// use matrix_sdk::Client; /// # use matrix_sdk::ruma::{ /// # api::client::filter::RoomEventFilter, /// # room_id, @@ -461,7 +461,8 @@ impl Common { let filter = assign!(RoomEventFilter::default(), { lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: false }, }); - let options = assign!(MessagesOptions::backward(token), { + let options = assign!(MessagesOptions::backward(), { + from: Some(token), limit: uint!(10), filter, }); @@ -1074,7 +1075,7 @@ impl Common { /// Options for [`messages`][Common::messages]. /// -/// See that method for details. +/// See that method and for details. #[derive(Debug)] #[non_exhaustive] pub struct MessagesOptions<'a> { @@ -1083,7 +1084,11 @@ pub struct MessagesOptions<'a> { /// This token can be obtained from a `prev_batch` token returned for each /// room from the sync API, or from a start or end token returned by a /// previous `messages` call. - pub from: &'a str, + /// + /// If `from` isn't provided the homeserver shall return a list of messages + /// from the first or last (per the value of the dir parameter) visible + /// event in the room history for the requesting user. + pub from: Option<&'a str>, /// The token to stop returning events at. /// @@ -1105,27 +1110,42 @@ pub struct MessagesOptions<'a> { } impl<'a> MessagesOptions<'a> { - /// Creates `MessagesOptions` with the given start token and direction. + /// Creates `MessagesOptions` with the given direction. /// /// All other parameters will be defaulted. - pub fn new(from: &'a str, dir: Direction) -> Self { - Self { from, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() } + pub fn new(dir: Direction) -> Self { + Self { from: None, to: None, dir, limit: uint!(10), filter: RoomEventFilter::default() } } - /// Creates `MessagesOptions` with the given start token, and `dir` set to - /// `Backward`. - pub fn backward(from: &'a str) -> Self { - Self::new(from, Direction::Backward) + /// Creates `MessagesOptions` with `dir` set to `Backward`. + /// + /// If no `from` token is set afterwards, pagination will start at the + /// end of (the accessible part of) the room timeline. + pub fn backward() -> Self { + Self::new(Direction::Backward) } - /// Creates `MessagesOptions` with the given start token, and `dir` set to - /// `Forward`. - pub fn forward(from: &'a str) -> Self { - Self::new(from, Direction::Forward) + /// Creates `MessagesOptions` with `dir` set to `Forward`. + /// + /// If no `from` token is set afterwards, pagination will start at the + /// beginning of (the accessible part of) the room timeline. + pub fn forward() -> Self { + Self::new(Direction::Forward) + } + + /// Creates a new `MessagesOptions` from `self` with the `from` field set to + /// the given value. + /// + /// Since the field is public, you can also assign to it directly. This + /// method merely acts as a shorthand for that, because it is very + /// common to set this field. + pub fn from(self, from: impl Into>) -> Self { + Self { from: from.into(), ..self } } fn into_request(self, room_id: &'a RoomId) -> get_message_events::v3::Request<'_> { - assign!(get_message_events::v3::Request::new(room_id, Some(self.from), self.dir), { + assign!(get_message_events::v3::Request::new(room_id, self.dir), { + from: self.from, to: self.to, limit: self.limit, filter: self.filter, diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index 184df9489..5ffad481e 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -20,7 +20,7 @@ use ruma::{ }, message::send_message_event, read_marker::set_read_marker, - receipt::create_receipt, + receipt::create_receipt::{self, v3::ReceiptType}, redact::redact_event, state::send_state_event, typing::create_typing_event::v3::{Request as TypingRequest, Typing}, @@ -30,7 +30,6 @@ use ruma::{ serde::Raw, EventId, OwnedTransactionId, TransactionId, UserId, }; -use ruma_client_api::receipt::create_receipt::v3::ReceiptType; use serde_json::Value; use tracing::debug; #[cfg(feature = "e2e-encryption")] diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 6fa924b21..a580e56c5 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -22,7 +22,7 @@ impl Client { rooms, presence, account_data, - to_device: _, + to_device, device_lists: _, device_one_time_keys_count: _, ambiguity_changes: _, @@ -31,6 +31,7 @@ impl Client { self.handle_sync_events(EventKind::GlobalAccountData, &None, &account_data.events).await?; self.handle_sync_events(EventKind::Presence, &None, &presence.events).await?; + self.handle_sync_events(EventKind::ToDevice, &None, &to_device.events).await?; for (room_id, room_info) in &rooms.join { let room = self.get_room(room_id); diff --git a/labs/sled-state-inspector/Cargo.toml b/labs/sled-state-inspector/Cargo.toml index 3255f6c0f..60a0366c7 100644 --- a/labs/sled-state-inspector/Cargo.toml +++ b/labs/sled-state-inspector/Cargo.toml @@ -10,7 +10,7 @@ clap = "3.2.4" futures = { version = "0.3.21", default-features = false, features = ["executor"] } matrix-sdk-base = { path = "../../crates/matrix-sdk-base", version = "0.5.0" } matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled", version = "0.1.0" } -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae" } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b" } rustyline = "9.1.2" rustyline-derive = "0.6.0" serde = "1.0.136" diff --git a/labs/sled-state-inspector/src/main.rs b/labs/sled-state-inspector/src/main.rs index 5c088d7c7..2785345c3 100644 --- a/labs/sled-state-inspector/src/main.rs +++ b/labs/sled-state-inspector/src/main.rs @@ -3,7 +3,7 @@ use std::{convert::TryFrom, fmt::Debug, sync::Arc}; use atty::Stream; use clap::{Arg, ArgMatches, Command as Argparse}; use futures::executor::block_on; -use matrix_sdk_base::{RoomInfo, Store}; +use matrix_sdk_base::RoomInfo; use matrix_sdk_sled::StateStore; use ruma::{events::StateEventType, OwnedRoomId, OwnedUserId, RoomId}; use rustyline::{ @@ -26,13 +26,13 @@ use syntect::{ #[derive(Clone)] struct Inspector { - store: Store, + store: Arc, printer: Printer, } #[derive(Helper)] struct InspectorHelper { - store: Store, + store: Arc, _highlighter: MatchingBracketHighlighter, _validator: MatchingBracketValidator, _hinter: HistoryHinter, @@ -54,7 +54,7 @@ impl InspectorHelper { "m.room.topic", ]; - fn new(store: Store) -> Self { + fn new(store: Arc) -> Self { Self { store, _highlighter: MatchingBracketHighlighter::new(), @@ -72,7 +72,9 @@ impl InspectorHelper { } fn complete_rooms(&self, arg: Option<&&str>) -> Vec { - let rooms: Vec = block_on(async { self.store.get_room_infos().await.unwrap() }); + let rooms: Vec = block_on(async { + matrix_sdk_base::StateStore::get_room_infos(&*self.store).await.unwrap() + }); rooms .into_iter() @@ -200,9 +202,8 @@ impl Printer { impl Inspector { fn new(database_path: &str, json: bool, color: bool) -> Self { let printer = Printer::new(json, color); - let store = Store::new(Arc::new( - StateStore::open_with_path(database_path).expect("Can't open sled database"), - )); + let store = + Arc::new(StateStore::open_with_path(database_path).expect("Can't open sled database")); Self { store, printer } } @@ -237,7 +238,8 @@ impl Inspector { } async fn list_rooms(&self) { - let rooms: Vec = self.store.get_room_infos().await.unwrap(); + let rooms: Vec = + matrix_sdk_base::StateStore::get_room_infos(&*self.store).await.unwrap(); self.printer.pretty_print_struct(&rooms); } @@ -247,7 +249,8 @@ impl Inspector { } async fn get_profiles(&self, room_id: OwnedRoomId) { - let joined: Vec = self.store.get_joined_user_ids(&room_id).await.unwrap(); + let joined: Vec = + matrix_sdk_base::StateStore::get_joined_user_ids(&*self.store, &room_id).await.unwrap(); for member in joined { let event = self.store.get_profile(&room_id, &member).await.unwrap(); @@ -256,7 +259,8 @@ impl Inspector { } async fn get_members(&self, room_id: OwnedRoomId) { - let joined: Vec = self.store.get_joined_user_ids(&room_id).await.unwrap(); + let joined: Vec = + matrix_sdk_base::StateStore::get_joined_user_ids(&*self.store, &room_id).await.unwrap(); for member in joined { let event = self.store.get_member_event(&room_id, &member).await.unwrap(); diff --git a/testing/matrix-sdk-test/Cargo.toml b/testing/matrix-sdk-test/Cargo.toml index 411201094..b94de0fe8 100644 --- a/testing/matrix-sdk-test/Cargo.toml +++ b/testing/matrix-sdk-test/Cargo.toml @@ -19,6 +19,6 @@ appservice = [] http = "0.2.6" matrix-sdk-test-macros = { version = "0.2.0", path = "../matrix-sdk-test-macros" } once_cell = "1.10.0" -ruma = { git = "https://github.com/ruma/ruma", rev = "7ec599e83d8ba6b2ace9e4fe184fc7560b0ff6ae", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ca8c66c885241a7ba3805399604eda4a38979f6b", features = ["client-api-c"] } serde = "1.0.136" serde_json = "1.0.79"