mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-17 04:58:41 -04:00
Replace ClientConfig with ClientBuilder
This includes a few not-strictly-related changes that made sense to do at the same time: * Check for a functioning homeserver via get_supported_versions regardless of whether a homeserver URL or user ID was supplied * Rename use_discovery_response to respect_login_well_known * Small appservice documentation improvement because those docs had to be touched anyways * Some test refactorings in tests that had to be touched anyways
This commit is contained in:
@@ -90,10 +90,9 @@ pub use matrix_sdk;
|
||||
pub use matrix_sdk::ruma;
|
||||
use matrix_sdk::{
|
||||
bytes::Bytes,
|
||||
config::ClientConfig,
|
||||
event_handler::{EventHandler, EventHandlerResult, SyncEvent},
|
||||
reqwest::Url,
|
||||
Client, Session,
|
||||
Client, ClientBuildError, ClientBuilder, Session,
|
||||
};
|
||||
use regex::Regex;
|
||||
use ruma::{
|
||||
@@ -210,8 +209,8 @@ impl AppService {
|
||||
/// Create new AppService
|
||||
///
|
||||
/// Also creates and caches a [`Client`] for the [`MainUser`].
|
||||
/// The default [`ClientConfig`] is used, if you want to customize it
|
||||
/// use [`Self::with_config()`] instead.
|
||||
/// A default [`ClientBuilder`] is used, if you want to customize it
|
||||
/// use [`with_client_builder()`][Self::with_client_builder] instead.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
@@ -228,19 +227,19 @@ impl AppService {
|
||||
registration: AppServiceRegistration,
|
||||
) -> Result<Self> {
|
||||
let appservice =
|
||||
Self::with_config(homeserver_url, server_name, registration, ClientConfig::default())
|
||||
Self::with_client_builder(homeserver_url, server_name, registration, Client::builder())
|
||||
.await?;
|
||||
|
||||
Ok(appservice)
|
||||
}
|
||||
|
||||
/// Same as [`Self::new()`] but lets you provide a [`ClientConfig`] for the
|
||||
/// [`Client`]
|
||||
pub async fn with_config(
|
||||
/// Same as [`new()`][Self::new] but lets you provide a [`ClientBuilder`]
|
||||
/// for the [`Client`]
|
||||
pub async fn with_client_builder(
|
||||
homeserver_url: impl TryInto<Url, Error = url::ParseError>,
|
||||
server_name: impl TryInto<Box<ServerName>, Error = identifiers::Error>,
|
||||
registration: AppServiceRegistration,
|
||||
client_config: ClientConfig,
|
||||
builder: ClientBuilder,
|
||||
) -> Result<Self> {
|
||||
let homeserver_url = homeserver_url.try_into()?;
|
||||
let server_name = server_name.try_into()?;
|
||||
@@ -253,7 +252,7 @@ impl AppService {
|
||||
AppService { homeserver_url, server_name, registration, clients, event_handler };
|
||||
|
||||
// we create and cache the [`MainUser`] by default
|
||||
appservice.create_and_cache_client(&sender_localpart, client_config).await?;
|
||||
appservice.create_and_cache_client(&sender_localpart, builder).await?;
|
||||
|
||||
Ok(appservice)
|
||||
}
|
||||
@@ -266,8 +265,9 @@ impl AppService {
|
||||
///
|
||||
/// This method is a singleton that saves the client internally for re-use
|
||||
/// based on the `localpart`. The cached [`Client`] can be retrieved either
|
||||
/// by calling this method again or by calling [`Self::get_cached_client()`]
|
||||
/// which is non-async convenience wrapper.
|
||||
/// by calling this method again or by calling
|
||||
/// [`get_cached_client()`][Self::get_cached_client] which is non-async
|
||||
/// convenience wrapper.
|
||||
///
|
||||
/// Note that if you want to do actions like joining rooms with a virtual
|
||||
/// user it needs to be registered first. `Self::register_virtual_user()`
|
||||
@@ -281,20 +281,20 @@ impl AppService {
|
||||
/// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion
|
||||
pub async fn virtual_user_client(&self, localpart: impl AsRef<str>) -> Result<Client> {
|
||||
let client =
|
||||
self.virtual_user_client_with_config(localpart, ClientConfig::default()).await?;
|
||||
self.virtual_user_client_with_client_builder(localpart, Client::builder()).await?;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
/// Same as [`Self::virtual_user_client()`] but with the ability to pass in
|
||||
/// a [`ClientConfig`]
|
||||
/// Same as [`virtual_user_client()`][Self::virtual_user_client] but with
|
||||
/// the ability to pass in a [`ClientBuilder`]
|
||||
///
|
||||
/// Since this method is a singleton follow-up calls with different
|
||||
/// [`ClientConfig`]s will be ignored.
|
||||
pub async fn virtual_user_client_with_config(
|
||||
/// [`ClientBuilder`]s will be ignored.
|
||||
pub async fn virtual_user_client_with_client_builder(
|
||||
&self,
|
||||
localpart: impl AsRef<str>,
|
||||
config: ClientConfig,
|
||||
builder: ClientBuilder,
|
||||
) -> Result<Client> {
|
||||
// TODO: check if localpart is covered by namespace?
|
||||
let localpart = localpart.as_ref();
|
||||
@@ -302,7 +302,7 @@ impl AppService {
|
||||
let client = if let Some(client) = self.clients.get(localpart) {
|
||||
client.clone()
|
||||
} else {
|
||||
self.create_and_cache_client(localpart, config).await?
|
||||
self.create_and_cache_client(localpart, builder).await?
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
@@ -311,7 +311,7 @@ impl AppService {
|
||||
async fn create_and_cache_client(
|
||||
&self,
|
||||
localpart: &str,
|
||||
mut config: ClientConfig,
|
||||
mut builder: ClientBuilder,
|
||||
) -> Result<Client> {
|
||||
let user_id = UserId::parse_with_server_name(localpart, &self.server_name)?;
|
||||
|
||||
@@ -319,11 +319,15 @@ impl AppService {
|
||||
// (`sender_localpart`) by default, so we don't need to assert identity
|
||||
// in that case
|
||||
if localpart != self.registration.sender_localpart {
|
||||
config = config.assert_identity();
|
||||
builder = builder.assert_identity();
|
||||
}
|
||||
|
||||
let client =
|
||||
Client::with_config(self.homeserver_url.clone(), config.appservice_mode()).await?;
|
||||
let client = builder
|
||||
.homeserver_url(self.homeserver_url.clone())
|
||||
.appservice_mode()
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)?;
|
||||
|
||||
let session = Session {
|
||||
access_token: self.registration.as_token.clone(),
|
||||
@@ -341,8 +345,9 @@ impl AppService {
|
||||
/// Get cached [`Client`]
|
||||
///
|
||||
/// Will return the client for the given `localpart` if previously
|
||||
/// constructed with [`Self::virtual_user_client()`] or
|
||||
/// [`Self::virtual_user_client_with_config()`].
|
||||
/// constructed with [`virtual_user_client()`][Self::virtual_user_client] or
|
||||
/// [`virtual_user_client_with_config()`][Self::
|
||||
/// virtual_user_client_with_client_builder].
|
||||
///
|
||||
/// If no `localpart` is given it assumes the [`MainUser`]'s `localpart`. If
|
||||
/// no client for `localpart` is found it will return an Error.
|
||||
|
||||
@@ -4,8 +4,9 @@ use std::{
|
||||
};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, RequestConfig},
|
||||
config::RequestConfig,
|
||||
ruma::{api::appservice::Registration, events::room::member::SyncRoomMemberEvent},
|
||||
Client,
|
||||
};
|
||||
use matrix_sdk_appservice::*;
|
||||
use matrix_sdk_test::{appservice::TransactionBuilder, async_test, EventsJson};
|
||||
@@ -32,10 +33,17 @@ async fn appservice(registration: Option<Registration>) -> Result<AppService> {
|
||||
let homeserver_url = mockito::server_url();
|
||||
let server_name = "localhost";
|
||||
|
||||
let client_config =
|
||||
ClientConfig::default().request_config(RequestConfig::default().disable_retry());
|
||||
let client_builder = Client::builder()
|
||||
.request_config(RequestConfig::default().disable_retry())
|
||||
.check_supported_versions(false);
|
||||
|
||||
AppService::with_config(homeserver_url.as_ref(), server_name, registration, client_config).await
|
||||
AppService::with_client_builder(
|
||||
homeserver_url.as_ref(),
|
||||
server_name,
|
||||
registration,
|
||||
client_builder,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, SyncSettings},
|
||||
room::Room,
|
||||
ruma::events::room::member::StrippedRoomMemberEvent,
|
||||
Client,
|
||||
config::SyncSettings, room::Room, ruma::events::room::member::StrippedRoomMemberEvent, Client,
|
||||
};
|
||||
use tokio::time::{sleep, Duration};
|
||||
use url::Url;
|
||||
|
||||
async fn on_stripped_state_member(
|
||||
room_member: StrippedRoomMemberEvent,
|
||||
@@ -44,11 +40,9 @@ async fn login_and_sync(
|
||||
homeserver_url: String,
|
||||
username: &str,
|
||||
password: &str,
|
||||
) -> Result<(), matrix_sdk::Error> {
|
||||
#[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))]
|
||||
let client_config = ClientConfig::new();
|
||||
#[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))]
|
||||
let mut client_config = ClientConfig::new();
|
||||
) -> anyhow::Result<()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut client_builder = Client::builder().homeserver_url(homeserver_url);
|
||||
|
||||
#[cfg(feature = "sled_state_store")]
|
||||
{
|
||||
@@ -56,17 +50,16 @@ async fn login_and_sync(
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("autojoin_bot");
|
||||
let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?;
|
||||
client_config = client_config.state_store(Box::new(state_store));
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexeddb_state_store")]
|
||||
{
|
||||
let state_store = matrix_sdk_indexeddb::StateStore::open();
|
||||
client_config = client_config.state_store(Box::new(state_store));
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||
let client = Client::with_config(homeserver_url, client_config).await.unwrap();
|
||||
let client = client_builder.build().await?;
|
||||
|
||||
client.login(username, password, None, Some("autojoin bot")).await?;
|
||||
|
||||
@@ -80,7 +73,7 @@ async fn login_and_sync(
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), matrix_sdk::Error> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (homeserver_url, username, password) =
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use std::{env, process::exit};
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{ClientConfig, SyncSettings},
|
||||
config::SyncSettings,
|
||||
room::Room,
|
||||
ruma::events::room::message::{
|
||||
MessageType, RoomMessageEventContent, SyncRoomMessageEvent, TextMessageEventContent,
|
||||
},
|
||||
Client,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
async fn on_room_message(event: SyncRoomMessageEvent, room: Room) {
|
||||
if let Room::Joined(room) = room {
|
||||
@@ -36,11 +35,9 @@ async fn login_and_sync(
|
||||
homeserver_url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
) -> Result<(), matrix_sdk::Error> {
|
||||
#[cfg(not(any(feature = "sled_state_store", feature = "indexeddb_state_store")))]
|
||||
let client_config = ClientConfig::new();
|
||||
#[cfg(any(feature = "sled_state_store", feature = "indexeddb_state_store"))]
|
||||
let mut client_config = ClientConfig::new();
|
||||
) -> anyhow::Result<()> {
|
||||
#[allow(unused_mut)]
|
||||
let mut client_builder = Client::builder().homeserver_url(homeserver_url);
|
||||
|
||||
#[cfg(feature = "sled_state_store")]
|
||||
{
|
||||
@@ -48,19 +45,16 @@ async fn login_and_sync(
|
||||
let mut home = dirs::home_dir().expect("no home directory found");
|
||||
home.push("party_bot");
|
||||
let state_store = matrix_sdk_sled::StateStore::open_with_path(home)?;
|
||||
client_config = client_config.state_store(Box::new(state_store));
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
#[cfg(feature = "indexeddb_state_store")]
|
||||
{
|
||||
let state_store = matrix_sdk_indexeddb::StateStore::open();
|
||||
client_config = client_config.state_store(Box::new(state_store));
|
||||
client_builder = client_builder.state_store(Box::new(state_store));
|
||||
}
|
||||
|
||||
let homeserver_url = Url::parse(&homeserver_url).expect("Couldn't parse the homeserver URL");
|
||||
// create a new Client with the given homeserver url and config
|
||||
let client = Client::with_config(homeserver_url, client_config).await.unwrap();
|
||||
|
||||
let client = client_builder.build().await.unwrap();
|
||||
client.login(&username, &password, None, Some("command bot")).await?;
|
||||
|
||||
println!("logged in as {}", username);
|
||||
@@ -84,7 +78,7 @@ async fn login_and_sync(
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), matrix_sdk::Error> {
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
let (homeserver_url, username, password) =
|
||||
|
||||
401
crates/matrix-sdk/src/client/builder.rs
Normal file
401
crates/matrix-sdk/src/client/builder.rs
Normal file
@@ -0,0 +1,401 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore};
|
||||
use ruma::{api::client::discover::discover_homeserver, UserId};
|
||||
use thiserror::Error;
|
||||
use url::Url;
|
||||
|
||||
use super::{Client, ClientInner};
|
||||
use crate::{
|
||||
config::RequestConfig,
|
||||
http_client::{HttpClient, HttpSend, HttpSettings},
|
||||
HttpError,
|
||||
};
|
||||
|
||||
/// Builder that allows creating and configuring various parts of a [`Client`].
|
||||
///
|
||||
/// When setting the `StateStore` it is up to the user to open/connect
|
||||
/// the storage backend before client creation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::Client;
|
||||
/// // To pass all the request through mitmproxy set the proxy and disable SSL
|
||||
/// // verification
|
||||
///
|
||||
/// let client_builder = Client::builder()
|
||||
/// .proxy("http://localhost:8080")?
|
||||
/// .disable_ssl_verification();
|
||||
/// ```
|
||||
///
|
||||
/// # Example for using a custom http client
|
||||
///
|
||||
/// Note: setting a custom http client will ignore `user_agent`, `proxy`, and
|
||||
/// `disable_ssl_verification` - you'd need to set these yourself if you want
|
||||
/// them.
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::Client;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// // setting up a custom http client
|
||||
/// let reqwest_builder = reqwest::ClientBuilder::new()
|
||||
/// .https_only(true)
|
||||
/// .no_proxy()
|
||||
/// .user_agent("MyApp/v3.0");
|
||||
///
|
||||
/// let client_builder = Client::builder()
|
||||
/// .http_client(Arc::new(reqwest_builder.build()?));
|
||||
/// # anyhow::Ok(())
|
||||
/// ```
|
||||
#[must_use]
|
||||
#[derive(Debug)]
|
||||
pub struct ClientBuilder {
|
||||
homeserver_cfg: Option<HomeserverConfig>,
|
||||
http_cfg: Option<HttpConfig>,
|
||||
store_config: StoreConfig,
|
||||
request_config: RequestConfig,
|
||||
respect_login_well_known: bool,
|
||||
appservice_mode: bool,
|
||||
check_supported_versions: bool,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
homeserver_cfg: None,
|
||||
http_cfg: None,
|
||||
store_config: Default::default(),
|
||||
request_config: Default::default(),
|
||||
respect_login_well_known: true,
|
||||
appservice_mode: false,
|
||||
check_supported_versions: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the homeserver URL to use.
|
||||
///
|
||||
/// This method is mutually exclusive with [`user_id()`][Self::user_id], if
|
||||
/// you set both whatever was set last will be used.
|
||||
pub fn homeserver_url(mut self, url: impl AsRef<str>) -> Self {
|
||||
self.homeserver_cfg = Some(HomeserverConfig::Url(url.as_ref().to_owned()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the user ID to discover the homeserver from.
|
||||
///
|
||||
/// This method is mutually exclusive with
|
||||
/// [`homeserver_url()`][Self::homeserver_url], if you set both whatever was
|
||||
/// set last will be used.
|
||||
pub fn user_id(mut self, user_id: &UserId) -> Self {
|
||||
self.homeserver_cfg = Some(HomeserverConfig::UserId(user_id.to_owned()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a new `ClientConfig` with the given [`StoreConfig`].
|
||||
///
|
||||
/// The easiest way to get a [`StoreConfig`] is to use the
|
||||
/// [`make_store_config`] method from the [`store`] module or directly from
|
||||
/// one of the store crates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `store_config` - The configuration of the store.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use matrix_sdk_base::store::MemoryStore;
|
||||
/// # let custom_state_store = Box::new(MemoryStore::new());
|
||||
/// use matrix_sdk::{Client, config::StoreConfig};
|
||||
///
|
||||
/// let store_config = StoreConfig::new().state_store(custom_state_store);
|
||||
/// let client_builder = Client::builder().store_config(store_config);
|
||||
/// ```
|
||||
/// [`make_store_config`]: crate::store::make_store_config
|
||||
/// [`store`]: crate::store
|
||||
pub fn store_config(mut self, store_config: StoreConfig) -> Self {
|
||||
self.store_config = store_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
///
|
||||
/// The state store should be opened before being set.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.store_config = self.store_config.state_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store should be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn crypto_store(
|
||||
mut self,
|
||||
store: Box<dyn matrix_sdk_base::crypto::store::CryptoStore>,
|
||||
) -> Self {
|
||||
self.store_config = self.store_config.crypto_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Update the client's homeserver URL with the discovery information
|
||||
/// present in the login response, if any.
|
||||
pub fn respect_login_well_known(mut self, value: bool) -> Self {
|
||||
self.respect_login_well_known = value;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default timeout, fail and retry behavior for all HTTP requests.
|
||||
pub fn request_config(mut self, request_config: RequestConfig) -> Self {
|
||||
self.request_config = request_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the proxy through which all the HTTP requests should go.
|
||||
///
|
||||
/// Note, only HTTP proxies are supported.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `proxy` - The HTTP URL of the proxy.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # futures::executor::block_on(async {
|
||||
/// use matrix_sdk::{Client, config::ClientConfig};
|
||||
///
|
||||
/// let client_config = ClientConfig::new()
|
||||
/// .proxy("http://localhost:8080")?;
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn proxy(mut self, proxy: impl AsRef<str>) -> Self {
|
||||
self.http_settings().proxy = Some(proxy.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable SSL verification for the HTTP requests.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn disable_ssl_verification(mut self) -> Self {
|
||||
self.http_settings().disable_ssl_verification = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom HTTP user agent for the client.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn user_agent(mut self, user_agent: impl AsRef<str>) -> Self {
|
||||
self.http_settings().user_agent = Some(user_agent.as_ref().to_owned());
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify an HTTP client to handle sending requests and receiving
|
||||
/// responses.
|
||||
///
|
||||
/// Any type that implements the `HttpSend` trait can be used to send /
|
||||
/// receive `http` types.
|
||||
///
|
||||
/// This method is mutually exclusive with
|
||||
/// [`user_agent()`][Self::user_agent],
|
||||
pub fn http_client(mut self, client: Arc<dyn HttpSend>) -> Self {
|
||||
self.http_cfg = Some(HttpConfig::Custom(client));
|
||||
self
|
||||
}
|
||||
|
||||
/// Puts the client into application service mode
|
||||
///
|
||||
/// This is low-level functionality. For an high-level API check the
|
||||
/// `matrix_sdk_appservice` crate.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "appservice")]
|
||||
pub fn appservice_mode(mut self) -> Self {
|
||||
self.appservice_mode = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "appservice")]
|
||||
pub fn assert_identity(mut self) -> Self {
|
||||
self.request_config.assert_identity = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Specify whether the homeserver functionality should be checked through a
|
||||
/// get_supported_versions request.
|
||||
///
|
||||
/// This is helpful for test code that doesn't care to mock that endpoint.
|
||||
#[doc(hidden)]
|
||||
pub fn check_supported_versions(mut self, value: bool) -> Self {
|
||||
self.check_supported_versions = value;
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn http_settings(&mut self) -> &mut HttpSettings {
|
||||
self.http_cfg.get_or_insert_with(Default::default).settings()
|
||||
}
|
||||
|
||||
/// Create a [`Client`] with the options set on this builder.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This method can fail for two general reasons:
|
||||
///
|
||||
/// * Invalid input: a missing or invalid homeserver URL or invalid proxy
|
||||
/// URL
|
||||
/// * HTTP error: If you supplied a user ID instead of a homeserver URL, a
|
||||
/// server discovery request is made which can fail; if you didn't set
|
||||
/// [`check_supported_versions(false)`][Self::check_supported_versions],
|
||||
/// that amounts to another request that can fail
|
||||
pub async fn build(self) -> Result<Client, ClientBuildError> {
|
||||
let homeserver_cfg = self.homeserver_cfg.ok_or(ClientBuildError::MissingHomeserver)?;
|
||||
|
||||
let inner_http_client = match self.http_cfg.unwrap_or_default() {
|
||||
#[allow(unused_mut)]
|
||||
HttpConfig::Settings(mut settings) => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
settings.timeout = self.request_config.timeout;
|
||||
}
|
||||
|
||||
Arc::new(settings.make_client()?)
|
||||
}
|
||||
HttpConfig::Custom(c) => c,
|
||||
};
|
||||
|
||||
let base_client = BaseClient::with_store_config(self.store_config);
|
||||
|
||||
let mk_http_client = |homeserver| {
|
||||
HttpClient::new(
|
||||
inner_http_client.clone(),
|
||||
homeserver,
|
||||
base_client.session().clone(),
|
||||
self.request_config,
|
||||
)
|
||||
};
|
||||
|
||||
let homeserver = match homeserver_cfg {
|
||||
HomeserverConfig::Url(url) => url,
|
||||
HomeserverConfig::UserId(user_id) => {
|
||||
let homeserver = homeserver_from_user_id(&user_id)?;
|
||||
let http_client = mk_http_client(Arc::new(RwLock::new(homeserver)));
|
||||
let well_known =
|
||||
http_client.send(discover_homeserver::Request::new(), None).await?;
|
||||
|
||||
well_known.homeserver.base_url
|
||||
}
|
||||
};
|
||||
|
||||
let homeserver = Arc::new(RwLock::new(Url::parse(&homeserver)?));
|
||||
let http_client = mk_http_client(homeserver.clone());
|
||||
|
||||
let inner = Arc::new(ClientInner {
|
||||
homeserver,
|
||||
http_client,
|
||||
base_client,
|
||||
#[cfg(feature = "encryption")]
|
||||
group_session_locks: Default::default(),
|
||||
#[cfg(feature = "encryption")]
|
||||
key_claim_lock: Default::default(),
|
||||
members_request_locks: Default::default(),
|
||||
typing_notice_times: Default::default(),
|
||||
event_handlers: Default::default(),
|
||||
event_handler_data: Default::default(),
|
||||
notification_handlers: Default::default(),
|
||||
appservice_mode: self.appservice_mode,
|
||||
respect_login_well_known: self.respect_login_well_known,
|
||||
sync_beat: event_listener::Event::new(),
|
||||
});
|
||||
let client = Client { inner };
|
||||
|
||||
if self.check_supported_versions {
|
||||
client.get_supported_versions().await?;
|
||||
}
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
|
||||
fn homeserver_from_user_id(user_id: &UserId) -> Result<Url, url::ParseError> {
|
||||
let homeserver = format!("https://{}", user_id.server_name());
|
||||
#[allow(unused_mut)]
|
||||
let mut result = Url::parse(homeserver.as_str())?;
|
||||
// Mockito only knows how to test http endpoints:
|
||||
// https://github.com/lipanski/mockito/issues/127
|
||||
#[cfg(test)]
|
||||
let _ = result.set_scheme("http");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HomeserverConfig {
|
||||
Url(String),
|
||||
UserId(Box<UserId>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum HttpConfig {
|
||||
Settings(HttpSettings),
|
||||
Custom(Arc<dyn HttpSend>),
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl HttpConfig {
|
||||
fn settings(&mut self) -> &mut HttpSettings {
|
||||
match self {
|
||||
Self::Settings(s) => s,
|
||||
Self::Custom(_) => {
|
||||
*self = Self::default();
|
||||
match self {
|
||||
Self::Settings(s) => s,
|
||||
Self::Custom(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HttpConfig {
|
||||
fn default() -> Self {
|
||||
Self::Settings(HttpSettings::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors that can happen in [`ClientBuilder::build`].
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ClientBuildError {
|
||||
/// No homeserver or user ID was configured
|
||||
#[error("no homeserver or user ID was configured")]
|
||||
MissingHomeserver,
|
||||
|
||||
/// An error encountered when trying to parse the homeserver url.
|
||||
#[error(transparent)]
|
||||
Url(#[from] url::ParseError),
|
||||
|
||||
/// Error doing an HTTP request.
|
||||
#[error(transparent)]
|
||||
Http(#[from] HttpError),
|
||||
}
|
||||
|
||||
impl ClientBuildError {
|
||||
/// Assert that a valid homeserver URL was given to the builder and no other
|
||||
/// invalid options were specified, which means the only possible error
|
||||
/// case is [`Self::Http`].
|
||||
#[doc(hidden)]
|
||||
pub fn assert_valid_builder_args(self) -> HttpError {
|
||||
match self {
|
||||
ClientBuildError::Http(e) => e,
|
||||
_ => unreachable!("homeserver URL was asserted to be valid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ use ruma::{
|
||||
capabilities::{get_capabilities, Capabilities},
|
||||
device::{delete_devices, get_devices},
|
||||
directory::{get_public_rooms, get_public_rooms_filtered},
|
||||
discover::{discover_homeserver, get_supported_versions},
|
||||
discover::get_supported_versions,
|
||||
filter::{create_filter::v3::Request as FilterUploadRequest, FilterDefinition},
|
||||
media::{create_content, get_content, get_content_thumbnail},
|
||||
membership::{join_room_by_id, join_room_by_id_or_alias},
|
||||
@@ -67,13 +67,17 @@ use url::Url;
|
||||
|
||||
use crate::{
|
||||
attachment::{AttachmentInfo, Thumbnail},
|
||||
config::{ClientConfig, RequestConfig},
|
||||
config::RequestConfig,
|
||||
error::{HttpError, HttpResult},
|
||||
event_handler::{EventHandler, EventHandlerData, EventHandlerResult, EventKind, SyncEvent},
|
||||
http_client::{client_with_config, HttpClient},
|
||||
http_client::HttpClient,
|
||||
room, Account, Error, Result,
|
||||
};
|
||||
|
||||
mod builder;
|
||||
|
||||
pub use self::builder::{ClientBuildError, ClientBuilder};
|
||||
|
||||
/// A conservative upload speed of 1Mbps
|
||||
const DEFAULT_UPLOAD_SPEED: u64 = 125_000;
|
||||
/// 5 min minimal upload request timeout, used to clamp the request timeout.
|
||||
@@ -139,7 +143,7 @@ pub(crate) struct ClientInner {
|
||||
appservice_mode: bool,
|
||||
/// Whether the client should update its homeserver URL with the discovery
|
||||
/// information present in the login response.
|
||||
use_discovery_response: bool,
|
||||
respect_login_well_known: bool,
|
||||
/// An event that can be listened on to wait for a successful sync. The
|
||||
/// event will only be fired if a sync loop is running. Can be used for
|
||||
/// synchronization, e.g. if we send out a request to create a room, we can
|
||||
@@ -161,53 +165,12 @@ impl Client {
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `homeserver_url` - The homeserver that the client should connect to.
|
||||
pub async fn new(homeserver_url: Url) -> Result<Self> {
|
||||
let config = ClientConfig::new();
|
||||
Client::with_config(homeserver_url, config).await
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] for the given homeserver and use the given
|
||||
/// configuration.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `homeserver_url` - The homeserver that the client should connect to.
|
||||
///
|
||||
/// * `config` - Configuration for the client.
|
||||
pub async fn with_config(homeserver_url: Url, config: ClientConfig) -> Result<Self> {
|
||||
let homeserver = Arc::new(RwLock::new(homeserver_url));
|
||||
|
||||
let client = if let Some(client) = config.client {
|
||||
client
|
||||
} else {
|
||||
Arc::new(client_with_config(&config)?)
|
||||
};
|
||||
|
||||
let base_client = BaseClient::with_store_config(config.store_config);
|
||||
let session = base_client.session().clone();
|
||||
|
||||
let http_client =
|
||||
HttpClient::new(client, homeserver.clone(), session, config.request_config);
|
||||
|
||||
let inner = Arc::new(ClientInner {
|
||||
homeserver,
|
||||
http_client,
|
||||
base_client,
|
||||
#[cfg(feature = "encryption")]
|
||||
group_session_locks: Default::default(),
|
||||
#[cfg(feature = "encryption")]
|
||||
key_claim_lock: Default::default(),
|
||||
members_request_locks: Default::default(),
|
||||
typing_notice_times: Default::default(),
|
||||
event_handlers: Default::default(),
|
||||
event_handler_data: Default::default(),
|
||||
notification_handlers: Default::default(),
|
||||
appservice_mode: config.appservice_mode,
|
||||
use_discovery_response: config.use_discovery_response,
|
||||
sync_beat: event_listener::Event::new(),
|
||||
});
|
||||
|
||||
Ok(Self { inner })
|
||||
pub async fn new(homeserver_url: Url) -> Result<Self, HttpError> {
|
||||
Self::builder()
|
||||
.homeserver_url(homeserver_url)
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] using homeserver auto discovery.
|
||||
@@ -240,37 +203,17 @@ impl Client {
|
||||
/// ```
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri
|
||||
pub async fn new_from_user_id(user_id: &UserId) -> Result<Self> {
|
||||
let config = ClientConfig::new();
|
||||
Client::new_from_user_id_with_config(user_id, config).await
|
||||
pub async fn new_from_user_id(user_id: &UserId) -> Result<Self, HttpError> {
|
||||
Self::builder()
|
||||
.user_id(user_id)
|
||||
.build()
|
||||
.await
|
||||
.map_err(ClientBuildError::assert_valid_builder_args)
|
||||
}
|
||||
|
||||
/// Create a new [`Client`] using homeserver auto discovery.
|
||||
///
|
||||
/// This method will create a [`Client`] object that will attempt to
|
||||
/// discover and configure the homeserver for the given user. Follows the
|
||||
/// homeserver discovery directions described in the [spec].
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `user_id` - The id of the user whose homeserver the client should
|
||||
/// connect to.
|
||||
///
|
||||
/// * `config` - Configuration for the client.
|
||||
///
|
||||
/// [spec]: https://spec.matrix.org/unstable/client-server-api/#well-known-uri
|
||||
pub async fn new_from_user_id_with_config(
|
||||
user_id: &UserId,
|
||||
config: ClientConfig,
|
||||
) -> Result<Self> {
|
||||
let homeserver = Client::homeserver_from_user_id(user_id)?;
|
||||
let client = Client::with_config(homeserver, config).await?;
|
||||
|
||||
let well_known = client.discover_homeserver().await?;
|
||||
let well_known = Url::parse(well_known.homeserver.base_url.as_ref())?;
|
||||
client.set_homeserver(well_known).await;
|
||||
client.get_supported_versions().await?;
|
||||
Ok(client)
|
||||
/// Create a new [`ClientBuilder`].
|
||||
pub fn builder() -> ClientBuilder {
|
||||
ClientBuilder::new()
|
||||
}
|
||||
|
||||
pub(crate) fn base_client(&self) -> &BaseClient {
|
||||
@@ -291,22 +234,6 @@ impl Client {
|
||||
self.base_client().mark_request_as_sent(request_id, response).await
|
||||
}
|
||||
|
||||
fn homeserver_from_user_id(user_id: &UserId) -> Result<Url> {
|
||||
let homeserver = format!("https://{}", user_id.server_name());
|
||||
#[allow(unused_mut)]
|
||||
let mut result = Url::parse(homeserver.as_str())?;
|
||||
// Mockito only knows how to test http endpoints:
|
||||
// https://github.com/lipanski/mockito/issues/127
|
||||
#[cfg(test)]
|
||||
let _ = result.set_scheme("http");
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
async fn discover_homeserver(&self) -> HttpResult<discover_homeserver::Response> {
|
||||
self.send(discover_homeserver::Request::new(), Some(RequestConfig::new().disable_retry()))
|
||||
.await
|
||||
}
|
||||
|
||||
/// Change the homeserver URL used by this client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -472,7 +399,12 @@ impl Client {
|
||||
/// use serde::{Deserialize, Serialize};
|
||||
///
|
||||
/// # block_on(async {
|
||||
/// # let client = Client::new(homeserver).await.unwrap();
|
||||
/// # let client = matrix_sdk::Client::builder()
|
||||
/// # .homeserver_url(homeserver)
|
||||
/// # .check_supported_versions(false)
|
||||
/// # .build()
|
||||
/// # .await
|
||||
/// # .unwrap();
|
||||
/// client
|
||||
/// .register_event_handler(
|
||||
/// |ev: SyncRoomMessageEvent, room: Room, client: Client| async move {
|
||||
@@ -586,7 +518,12 @@ impl Client {
|
||||
/// # fn obtain_gui_handle() -> SomeType { SomeType }
|
||||
/// # let homeserver = url::Url::parse("http://localhost:8080").unwrap();
|
||||
/// # block_on(async {
|
||||
/// # let client = matrix_sdk::Client::new(homeserver).await.unwrap();
|
||||
/// # let client = matrix_sdk::Client::builder()
|
||||
/// # .homeserver_url(homeserver)
|
||||
/// # .check_supported_versions(false)
|
||||
/// # .build()
|
||||
/// # .await
|
||||
/// # .unwrap();
|
||||
///
|
||||
/// // Handle used to send messages to the UI part of the app
|
||||
/// let my_gui_handle: SomeType = obtain_gui_handle();
|
||||
@@ -1135,7 +1072,7 @@ impl Client {
|
||||
///
|
||||
/// * `response` - A successful login response.
|
||||
async fn receive_login_response(&self, response: &login::v3::Response) -> Result<()> {
|
||||
if self.inner.use_discovery_response {
|
||||
if self.inner.respect_login_well_known {
|
||||
if let Some(well_known) = &response.well_known {
|
||||
if let Ok(homeserver) = Url::parse(&well_known.homeserver.base_url) {
|
||||
self.set_homeserver(homeserver).await;
|
||||
@@ -2325,6 +2262,7 @@ impl Client {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// mockito (the http mocking library) is not supported for wasm32
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
pub(crate) mod test {
|
||||
@@ -2367,26 +2305,38 @@ pub(crate) mod test {
|
||||
mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId,
|
||||
};
|
||||
use serde_json::json;
|
||||
use url::Url;
|
||||
|
||||
use super::{Client, Session, Url};
|
||||
use super::{Client, ClientBuilder, Session};
|
||||
use crate::{
|
||||
attachment::{
|
||||
AttachmentConfig, AttachmentInfo, BaseImageInfo, BaseThumbnailInfo, BaseVideoInfo,
|
||||
Thumbnail,
|
||||
},
|
||||
config::{ClientConfig, RequestConfig, SyncSettings},
|
||||
config::{RequestConfig, SyncSettings},
|
||||
HttpError, RoomMember,
|
||||
};
|
||||
|
||||
fn test_client_builder() -> ClientBuilder {
|
||||
let homeserver = Url::parse(&mockito::server_url()).unwrap();
|
||||
Client::builder().homeserver_url(homeserver).check_supported_versions(false)
|
||||
}
|
||||
|
||||
async fn no_retry_test_client() -> Client {
|
||||
test_client_builder()
|
||||
.request_config(RequestConfig::new().disable_retry())
|
||||
.build()
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) async fn logged_in_client() -> Client {
|
||||
let session = Session {
|
||||
access_token: "1234".to_owned(),
|
||||
user_id: user_id!("@example:localhost").to_owned(),
|
||||
device_id: device_id!("DEVICEID").to_owned(),
|
||||
};
|
||||
let homeserver = url::Url::parse(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry());
|
||||
let client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
client.restore_login(session).await.unwrap();
|
||||
|
||||
client
|
||||
@@ -2394,12 +2344,8 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn set_homeserver() {
|
||||
let client = no_retry_test_client().await;
|
||||
let homeserver = Url::from_str("http://example.com/").unwrap();
|
||||
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
client.set_homeserver(homeserver.clone()).await;
|
||||
|
||||
assert_eq!(client.homeserver().await, homeserver);
|
||||
@@ -2450,8 +2396,7 @@ pub(crate) mod test {
|
||||
#[async_test]
|
||||
async fn login() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
let client = Client::new(homeserver.clone()).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_types = mock("GET", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2482,10 +2427,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_with_discovery() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new().use_discovery_response();
|
||||
|
||||
let client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2502,10 +2444,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_no_discovery() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::new().use_discovery_response();
|
||||
|
||||
let client = Client::with_config(homeserver.clone(), config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m_login = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2517,7 +2456,7 @@ pub(crate) mod test {
|
||||
let logged_in = client.logged_in().await;
|
||||
assert!(logged_in, "Client should be logged in");
|
||||
|
||||
assert_eq!(client.homeserver().await, homeserver);
|
||||
assert_eq!(client.homeserver().await, Url::parse(&mockito::server_url()).unwrap());
|
||||
}
|
||||
|
||||
#[cfg(feature = "sso_login")]
|
||||
@@ -2529,7 +2468,7 @@ pub(crate) mod test {
|
||||
.create();
|
||||
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
let idp = crate::client::get_login_types::v3::IdentityProvider::new(
|
||||
"some-id".to_owned(),
|
||||
"idp-name".to_owned(),
|
||||
@@ -2564,9 +2503,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_with_sso_token() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("GET", "/_matrix/client/r0/login")
|
||||
.with_status(200)
|
||||
@@ -2610,7 +2547,6 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn test_join_leave_room() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
|
||||
@@ -2633,8 +2569,7 @@ pub(crate) mod test {
|
||||
assert!(room.is_some());
|
||||
|
||||
// test store reloads with correct room state from the state store
|
||||
let config = ClientConfig::new().request_config(RequestConfig::new().disable_retry());
|
||||
let joined_client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let joined_client = no_retry_test_client().await;
|
||||
joined_client.restore_login(session).await.unwrap();
|
||||
|
||||
// joined room reloaded from state store
|
||||
@@ -2694,9 +2629,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn login_error() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().disable_retry());
|
||||
let client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", "/_matrix/client/r0/login")
|
||||
.with_status(403)
|
||||
@@ -2724,8 +2657,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn register_error() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", Matcher::Regex(r"^/_matrix/client/r0/register\?.*$".to_owned()))
|
||||
.with_status(403)
|
||||
@@ -2868,8 +2800,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn room_search_all() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/publicRooms".to_owned()))
|
||||
.with_status(200)
|
||||
@@ -3554,8 +3485,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn delete_devices() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = no_retry_test_client().await;
|
||||
|
||||
let _m = mock("POST", "/_matrix/client/r0/delete_devices")
|
||||
.with_status(401)
|
||||
@@ -3610,10 +3540,13 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn retry_limit_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3));
|
||||
assert!(config.request_config.retry_limit.unwrap() == 3);
|
||||
let client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_limit(3))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(client.inner.http_client.request_config.retry_limit.unwrap() == 3);
|
||||
|
||||
let m = mock("POST", "/_matrix/client/r0/login").with_status(501).expect(3).create();
|
||||
|
||||
@@ -3626,13 +3559,15 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn retry_timeout_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
// Keep this timeout small so that the test doesn't take long
|
||||
let retry_timeout = Duration::from_secs(5);
|
||||
let config = ClientConfig::default()
|
||||
.request_config(RequestConfig::new().retry_timeout(retry_timeout));
|
||||
assert!(config.request_config.retry_timeout.unwrap() == retry_timeout);
|
||||
let client = Client::with_config(homeserver, config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_timeout(retry_timeout))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(client.inner.http_client.request_config.retry_timeout.unwrap() == retry_timeout);
|
||||
|
||||
let m =
|
||||
mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(2).create();
|
||||
@@ -3646,8 +3581,7 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn short_retry_initial_http_requests() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let client = Client::new(homeserver).await.unwrap();
|
||||
let client = test_client_builder().build().await.unwrap();
|
||||
|
||||
let m =
|
||||
mock("POST", "/_matrix/client/r0/login").with_status(501).expect_at_least(3).create();
|
||||
@@ -3759,7 +3693,6 @@ pub(crate) mod test {
|
||||
|
||||
#[async_test]
|
||||
async fn test_state_event_getting() {
|
||||
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||
let room_id = room_id!("!SVkFJHzfwvuaIEawgC:localhost");
|
||||
|
||||
let session = Session {
|
||||
@@ -3828,8 +3761,11 @@ pub(crate) mod test {
|
||||
.with_body(sync.to_string())
|
||||
.create();
|
||||
|
||||
let config = ClientConfig::default().request_config(RequestConfig::new().retry_limit(3));
|
||||
let client = Client::with_config(homeserver.clone(), config).await.unwrap();
|
||||
let client = test_client_builder()
|
||||
.request_config(RequestConfig::new().retry_limit(3))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
client.restore_login(session.clone()).await.unwrap();
|
||||
|
||||
let room = client.get_joined_room(room_id);
|
||||
@@ -1,252 +0,0 @@
|
||||
// Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use std::{
|
||||
fmt::{self, Debug},
|
||||
path::Path,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use http::header::InvalidHeaderValue;
|
||||
use matrix_sdk_base::StateStore;
|
||||
|
||||
use crate::{
|
||||
config::{RequestConfig, StoreConfig},
|
||||
HttpSend, Result,
|
||||
};
|
||||
|
||||
/// Configuration for the creation of the `Client`.
|
||||
///
|
||||
/// When setting the `StateStore` it is up to the user to open/connect
|
||||
/// the storage backend before client creation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::config::ClientConfig;
|
||||
/// // To pass all the request through mitmproxy set the proxy and disable SSL
|
||||
/// // verification
|
||||
///
|
||||
/// # futures::executor::block_on(async {
|
||||
/// let client_config = ClientConfig::new()
|
||||
/// .proxy("http://localhost:8080")?
|
||||
/// .disable_ssl_verification();
|
||||
/// # matrix_sdk::Result::<()>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// # Example for using a custom client
|
||||
/// Note: setting a custom client will ignore `user_agent`, `proxy`, and
|
||||
/// `disable_ssl_verification` - you'd need to set these yourself if you
|
||||
/// want them.
|
||||
///
|
||||
/// ```
|
||||
/// use matrix_sdk::config::ClientConfig;
|
||||
/// use reqwest::ClientBuilder;
|
||||
/// use std::sync::Arc;
|
||||
///
|
||||
/// // setting up a custom builder
|
||||
/// let builder = ClientBuilder::new()
|
||||
/// .https_only(true)
|
||||
/// .no_proxy()
|
||||
/// .user_agent("MyApp/v3.0");
|
||||
///
|
||||
/// # futures::executor::block_on(async {
|
||||
/// let client_config = ClientConfig::new()
|
||||
/// .client(Arc::new(builder.build()?));
|
||||
/// # matrix_sdk::Result::<()>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[derive(Default)]
|
||||
pub struct ClientConfig {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) proxy: Option<reqwest::Proxy>,
|
||||
pub(crate) user_agent: Option<String>,
|
||||
pub(crate) disable_ssl_verification: bool,
|
||||
pub(crate) store_config: StoreConfig,
|
||||
pub(crate) request_config: RequestConfig,
|
||||
pub(crate) client: Option<Arc<dyn HttpSend>>,
|
||||
pub(crate) appservice_mode: bool,
|
||||
pub(crate) use_discovery_response: bool,
|
||||
}
|
||||
|
||||
#[cfg(not(tarpaulin_include))]
|
||||
impl Debug for ClientConfig {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut res = fmt.debug_struct("ClientConfig");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = res.field("proxy", &self.proxy);
|
||||
|
||||
res.field("user_agent", &self.user_agent)
|
||||
.field("disable_ssl_verification", &self.disable_ssl_verification)
|
||||
.field("request_config", &self.request_config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientConfig {
|
||||
/// Create a new default `ClientConfig`.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new `ClientConfig` with the given [`StoreConfig`].
|
||||
///
|
||||
/// The easiest way to get a [`StoreConfig`] is to use the
|
||||
/// [`make_store_config`] method from the [`store`] module or directly from
|
||||
/// one of the store crates.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `store_config` - The configuration of the store.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # futures::executor::block_on(async {
|
||||
/// # use matrix_sdk_base::store::MemoryStore;
|
||||
/// # let custom_state_store = Box::new(MemoryStore::new());
|
||||
/// use matrix_sdk::{Client, config::{ClientConfig, StoreConfig}};
|
||||
///
|
||||
/// let store_config = StoreConfig::new().state_store(custom_state_store);
|
||||
/// let client_config = ClientConfig::new()
|
||||
/// .store_config(store_config)
|
||||
/// .use_discovery_response();
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
/// [`make_store_config`]: crate::store::make_store_config
|
||||
/// [`store`]: crate::store
|
||||
pub fn store_config(mut self, store_config: StoreConfig) -> Self {
|
||||
self.store_config = store_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the proxy through which all the HTTP requests should go.
|
||||
///
|
||||
/// Note, only HTTP proxies are supported.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `proxy` - The HTTP URL of the proxy.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # futures::executor::block_on(async {
|
||||
/// use matrix_sdk::{Client, config::ClientConfig};
|
||||
///
|
||||
/// let client_config = ClientConfig::new()
|
||||
/// .proxy("http://localhost:8080")?;
|
||||
///
|
||||
/// # Result::<_, matrix_sdk::Error>::Ok(())
|
||||
/// # });
|
||||
/// ```
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn proxy(mut self, proxy: &str) -> Result<Self> {
|
||||
self.proxy = Some(reqwest::Proxy::all(proxy)?);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Disable SSL verification for the HTTP requests.
|
||||
#[must_use]
|
||||
pub fn disable_ssl_verification(mut self) -> Self {
|
||||
self.disable_ssl_verification = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom HTTP user agent for the client.
|
||||
pub fn user_agent(mut self, user_agent: &str) -> Result<Self, InvalidHeaderValue> {
|
||||
self.user_agent = Some(user_agent.to_owned());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `StateStore`.
|
||||
///
|
||||
/// The state store should be opened before being set.
|
||||
pub fn state_store(mut self, store: Box<dyn StateStore>) -> Self {
|
||||
self.store_config = self.store_config.state_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the default timeout, fail and retry behavior for all HTTP requests.
|
||||
#[must_use]
|
||||
pub fn request_config(mut self, request_config: RequestConfig) -> Self {
|
||||
self.request_config = request_config;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the [`RequestConfig`]
|
||||
pub fn get_request_config(&self) -> &RequestConfig {
|
||||
&self.request_config
|
||||
}
|
||||
|
||||
/// Specify a client to handle sending requests and receiving responses.
|
||||
///
|
||||
/// Any type that implements the `HttpSend` trait can be used to
|
||||
/// send/receive `http` types.
|
||||
#[must_use]
|
||||
pub fn client(mut self, client: Arc<dyn HttpSend>) -> Self {
|
||||
self.client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
/// Puts the client into application service mode
|
||||
///
|
||||
/// This is low-level functionality. For an high-level API check the
|
||||
/// `matrix_sdk_appservice` crate.
|
||||
#[cfg(feature = "appservice")]
|
||||
#[must_use]
|
||||
pub fn appservice_mode(mut self) -> Self {
|
||||
self.appservice_mode = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a custom implementation of a `CryptoStore`.
|
||||
///
|
||||
/// The crypto store should be opened before being set.
|
||||
#[cfg(feature = "encryption")]
|
||||
#[must_use]
|
||||
pub fn crypto_store(
|
||||
mut self,
|
||||
store: Box<dyn matrix_sdk_base::crypto::store::CryptoStore>,
|
||||
) -> Self {
|
||||
self.store_config = self.store_config.crypto_store(store);
|
||||
self
|
||||
}
|
||||
|
||||
/// Update the client's homeserver URL with the discovery information
|
||||
/// present in the login response, if any.
|
||||
#[must_use]
|
||||
pub fn use_discovery_response(mut self) -> Self {
|
||||
self.use_discovery_response = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// All outgoing http requests will have a GET query key-value appended with
|
||||
/// `user_id` being the key and the `user_id` from the `Session` being
|
||||
/// the value. Will error if there's no `Session`. This is called
|
||||
/// [identity assertion] in the Matrix Application Service Spec
|
||||
///
|
||||
/// [identity assertion]: https://spec.matrix.org/unstable/application-service-api/#identity-assertion
|
||||
#[cfg(feature = "appservice")]
|
||||
#[must_use]
|
||||
pub fn assert_identity(mut self) -> Self {
|
||||
self.request_config.assert_identity = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -14,11 +14,9 @@
|
||||
|
||||
//! Configuration to change the behaviour of the [`Client`][crate::Client].
|
||||
|
||||
mod client;
|
||||
mod request;
|
||||
mod sync;
|
||||
|
||||
pub use client::ClientConfig;
|
||||
pub use matrix_sdk_base::store::StoreConfig;
|
||||
pub use request::RequestConfig;
|
||||
pub use sync::SyncSettings;
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
use crate::http_client::DEFAULT_REQUEST_TIMEOUT;
|
||||
|
||||
/// Configuration for requests the `Client` makes.
|
||||
///
|
||||
|
||||
@@ -168,7 +168,7 @@ to work.
|
||||
2. To persist the encryption keys, you can use one of the provided backend
|
||||
constructors as described in the documentation of the [`store`] module or you
|
||||
can provide your own backend that implements [`CryptoStore`] in a
|
||||
[`StoreConfig`] or via [`ClientConfig::crypto_store()`].
|
||||
[`StoreConfig`] or via [`ClientBuilder::crypto_store()`].
|
||||
|
||||
## Restoring a client
|
||||
|
||||
@@ -233,4 +233,4 @@ is **not** supported using the default store.
|
||||
[`store`]: crate::store
|
||||
[`CryptoStore`]: matrix_sdk_base::crypto::store::CryptoStore
|
||||
[`StoreConfig`]: crate::config::StoreConfig
|
||||
[`ClientConfig::crypto_store()`]: crate::config::ClientConfig::crypto_store()
|
||||
[`ClientBuilder::crypto_store()`]: crate::ClientBuilder::crypto_store()
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{convert::TryFrom, fmt::Debug, sync::Arc};
|
||||
use std::{convert::TryFrom, fmt::Debug, sync::Arc, time::Duration};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::Response as HttpResponse;
|
||||
@@ -25,11 +25,9 @@ use ruma::api::{
|
||||
use tracing::trace;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
config::{ClientConfig, RequestConfig},
|
||||
error::HttpError,
|
||||
Session,
|
||||
};
|
||||
use crate::{config::RequestConfig, error::HttpError, Session};
|
||||
|
||||
pub(crate) const DEFAULT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
|
||||
|
||||
/// Abstraction around the http layer. The allows implementors to use different
|
||||
/// http libraries.
|
||||
@@ -203,33 +201,58 @@ impl HttpClient {
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a client with the specified configuration.
|
||||
pub(crate) fn client_with_config(config: &ClientConfig) -> Result<Client, HttpError> {
|
||||
let http_client = reqwest::Client::builder();
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct HttpSettings {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let http_client = {
|
||||
let http_client = if config.disable_ssl_verification {
|
||||
http_client.danger_accept_invalid_certs(true)
|
||||
} else {
|
||||
http_client
|
||||
pub(crate) disable_ssl_verification: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) proxy: Option<String>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) user_agent: Option<String>,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) timeout: Duration,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for HttpSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
disable_ssl_verification: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
proxy: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
user_agent: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
timeout: DEFAULT_REQUEST_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpSettings {
|
||||
/// Build a client with the specified configuration.
|
||||
pub(crate) fn make_client(&self) -> Result<Client, HttpError> {
|
||||
#[allow(unused_mut)]
|
||||
let mut http_client = reqwest::Client::builder();
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
if self.disable_ssl_verification {
|
||||
http_client = http_client.danger_accept_invalid_certs(true)
|
||||
}
|
||||
|
||||
if let Some(p) = &self.proxy {
|
||||
http_client = http_client.proxy(reqwest::Proxy::all(p.as_str())?);
|
||||
}
|
||||
|
||||
let user_agent =
|
||||
self.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned());
|
||||
|
||||
http_client = http_client.user_agent(user_agent).timeout(self.timeout);
|
||||
};
|
||||
|
||||
let http_client = match &config.proxy {
|
||||
Some(p) => http_client.proxy(p.clone()),
|
||||
None => http_client,
|
||||
};
|
||||
|
||||
let user_agent = config.user_agent.clone().unwrap_or_else(|| "matrix-rust-sdk".to_owned());
|
||||
|
||||
http_client.user_agent(user_agent).timeout(config.request_config.timeout)
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[allow(unused)]
|
||||
let _ = config;
|
||||
|
||||
Ok(http_client.build()?)
|
||||
Ok(http_client.build()?)
|
||||
}
|
||||
}
|
||||
|
||||
async fn response_to_http_response(
|
||||
|
||||
@@ -57,7 +57,7 @@ mod sync;
|
||||
pub mod encryption;
|
||||
|
||||
pub use account::Account;
|
||||
pub use client::{Client, LoopCtrl};
|
||||
pub use client::{Client, ClientBuildError, ClientBuilder, LoopCtrl};
|
||||
#[cfg(feature = "image_proc")]
|
||||
pub use error::ImageError;
|
||||
pub use error::{Error, HttpError, HttpResult, Result};
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
//! implementation for WebAssembly.
|
||||
//!
|
||||
//! Both options provide a `make_store_config` convenience method to create a
|
||||
//! [`StoreConfig`] for [`ClientConfig::store_config()`].
|
||||
//! [`StoreConfig`] for [`ClientBuilder::store_config()`].
|
||||
//!
|
||||
//! [`StoreConfig`]: crate::config::StoreConfig
|
||||
//! [`ClientConfig::store_config()`]: crate::config::ClientConfig::store_config()
|
||||
//! [`ClientBuilder::store_config()`]: crate::ClientBuilder::store_config
|
||||
|
||||
#[cfg(any(feature = "indexeddb_state_store", feature = "indexeddb_cryptostore"))]
|
||||
pub use matrix_sdk_indexeddb::*;
|
||||
|
||||
Reference in New Issue
Block a user