Merge remote-tracking branch 'origin/main' into gnunicorn/issue833

This commit is contained in:
Benjamin Kampmann
2022-07-21 17:37:49 +02:00
40 changed files with 968 additions and 782 deletions

View File

@@ -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}}

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:
@@ -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 }}

120
CONVENTIONAL_COMMITS.md Normal file
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.

View File

@@ -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"] }

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,3 +1,32 @@
# Matrix-Rust-SDK NodeJS Bindings
# Matrix-Rust-SDK Node.js Bindings
-- Changelog Test --
## 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).

View File

@@ -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" }

View File

@@ -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": {

View File

@@ -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"

View File

@@ -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);

View File

@@ -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<Option<Arc<Client>>>,
homeserver_details: RwLock<Option<Arc<HomeserverLoginDetails>>>,
}
#[derive(Debug, thiserror::Error)]
@@ -23,52 +25,67 @@ 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> {
pub fn homeserver_details(&self) -> Option<Arc<HomeserverLoginDetails>> {
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<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 })
}
}

View File

@@ -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<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) {

View File

@@ -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<Runtime> =
@@ -55,3 +56,10 @@ impl From<anyhow::Error> 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();
}

View File

@@ -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"

View File

@@ -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),

View File

@@ -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<Bytes>,
) -> Result<http::Request<Bytes>> {
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<String>,
registration: Option<Registration>,
) -> Result<AppService> {
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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
];
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(&registration_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(())
}
}
}

View File

@@ -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::<http::Request<Bytes>, Rejection>(request)
}))
.and(
http_request().and_then(|request| async move {
Ok::<http::Request<Bytes>, Rejection>(request)
}),
)
.boxed()
}

View File

@@ -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<String>,
registration: Option<Registration>,
) -> Result<AppService> {
// 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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
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::<AnyRoomEvent>(),
];
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(&registration_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(())
}
}

View File

@@ -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"] }

View File

@@ -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<Room> {
self.store.get_rooms()
}
/// Get all the rooms this client knows about.
pub fn get_stripped_rooms(&self) -> Vec<Room> {
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.

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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<dyn StateStore>) -> StoreResult<Store> {
pub async fn populate_store(store: Arc<dyn StateStore>) -> 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<AnySyncStateEvent> {
@@ -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?;

View File

@@ -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<dyn StateStore>,
session: Arc<OnceCell<Session>>,
/// The current sync token that should be used for the next sync call.

View File

@@ -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]

View File

@@ -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]

View File

@@ -1098,12 +1098,10 @@ impl RequestState<Ready> {
// 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
};

View File

@@ -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"

View File

@@ -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]

View File

@@ -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"

View File

@@ -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"

View File

@@ -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<room::Room> {
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<room::Joined> {
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<room::Invited> {
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<room::Left> {
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<room::Room> {
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<room::Joined> {
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<room::Invited> {
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<room::Left> {
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

View File

@@ -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<Messages> {
@@ -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 <https://spec.matrix.org/v1.3/client-server-api/#get_matrixclientv3roomsroomidmessages> 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<Option<&'a str>>) -> 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,

View File

@@ -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")]

View File

@@ -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);

View File

@@ -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"

View File

@@ -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<StateStore>,
printer: Printer,
}
#[derive(Helper)]
struct InspectorHelper {
store: Store,
store: Arc<StateStore>,
_highlighter: MatchingBracketHighlighter,
_validator: MatchingBracketValidator,
_hinter: HistoryHinter,
@@ -54,7 +54,7 @@ impl InspectorHelper {
"m.room.topic",
];
fn new(store: Store) -> Self {
fn new(store: Arc<StateStore>) -> Self {
Self {
store,
_highlighter: MatchingBracketHighlighter::new(),
@@ -72,7 +72,9 @@ impl InspectorHelper {
}
fn complete_rooms(&self, arg: Option<&&str>) -> Vec<Pair> {
let rooms: Vec<RoomInfo> = block_on(async { self.store.get_room_infos().await.unwrap() });
let rooms: Vec<RoomInfo> = 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<RoomInfo> = self.store.get_room_infos().await.unwrap();
let rooms: Vec<RoomInfo> =
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<OwnedUserId> = self.store.get_joined_user_ids(&room_id).await.unwrap();
let joined: Vec<OwnedUserId> =
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<OwnedUserId> = self.store.get_joined_user_ids(&room_id).await.unwrap();
let joined: Vec<OwnedUserId> =
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();

View File

@@ -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"