client: Store supported Matrix versions in Client

… and refactor creation of Client.
This commit is contained in:
Jonas Platte 2022-02-19 00:05:38 +01:00
parent 3e0fb2f46d
commit 282abc9dc2
No known key found for this signature in database
GPG Key ID: 7D261D771D915378
7 changed files with 202 additions and 145 deletions

View File

@ -17,10 +17,13 @@ use ruma_common::presence::PresenceState;
use ruma_identifiers::{DeviceId, UserId};
use crate::{
add_user_id_to_query, send_customized_request, DefaultConstructibleHttpClient, Error,
HttpClient, ResponseError, ResponseResult,
add_user_id_to_query, send_customized_request, Error, HttpClient, ResponseError, ResponseResult,
};
mod builder;
pub use self::builder::ClientBuilder;
/// A client for the Matrix client-server API.
#[derive(Clone, Debug)]
pub struct Client<C>(Arc<ClientData<C>>);
@ -34,26 +37,21 @@ struct ClientData<C> {
/// The underlying HTTP client.
http_client: C,
/// User session data.
/// The access token, if logged in.
access_token: Mutex<Option<String>>,
/// The (known) Matrix versions the homeserver supports.
supported_matrix_versions: Vec<MatrixVersion>,
}
impl Client<()> {
/// Creates a new client builder.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}
}
impl<C> Client<C> {
/// Creates a new client using the given underlying HTTP client.
///
/// This allows the user to configure the details of HTTP as desired.
pub fn with_http_client(
http_client: C,
homeserver_url: String,
access_token: Option<String>,
) -> Self {
Self(Arc::new(ClientData {
homeserver_url,
http_client,
access_token: Mutex::new(access_token),
}))
}
/// Get a copy of the current `access_token`, if any.
///
/// Useful for serializing and persisting the session to be restored later.
@ -62,32 +60,16 @@ impl<C> Client<C> {
}
}
impl<C: DefaultConstructibleHttpClient> Client<C> {
/// Creates a new client based on a default-constructed hyper HTTP client.
pub fn new(homeserver_url: String, access_token: Option<String>) -> Self {
Self(Arc::new(ClientData {
homeserver_url,
http_client: DefaultConstructibleHttpClient::default(),
access_token: Mutex::new(access_token),
}))
}
}
impl<C: HttpClient> Client<C> {
/// Makes a request to a Matrix API endpoint.
pub async fn send_request<R: OutgoingRequest>(
&self,
request: R,
for_versions: &[MatrixVersion],
) -> ResponseResult<C, R> {
self.send_customized_request(request, for_versions, |_| Ok(())).await
pub async fn send_request<R: OutgoingRequest>(&self, request: R) -> ResponseResult<C, R> {
self.send_customized_request(request, |_| Ok(())).await
}
/// Makes a request to a Matrix API endpoint including additional URL parameters.
pub async fn send_customized_request<R, F>(
&self,
request: R,
for_versions: &[MatrixVersion],
customize: F,
) -> ResponseResult<C, R>
where
@ -104,7 +86,7 @@ impl<C: HttpClient> Client<C> {
&self.0.http_client,
&self.0.homeserver_url,
send_access_token,
for_versions,
&self.0.supported_matrix_versions,
request,
customize,
)
@ -119,10 +101,8 @@ impl<C: HttpClient> Client<C> {
&self,
user_id: &UserId,
request: R,
for_versions: &[MatrixVersion],
) -> ResponseResult<C, R> {
self.send_customized_request(request, for_versions, add_user_id_to_query::<C, R>(user_id))
.await
self.send_customized_request(request, add_user_id_to_query::<C, R>(user_id)).await
}
/// Log in with a username and password.
@ -142,7 +122,7 @@ impl<C: HttpClient> Client<C> {
device_id,
initial_device_display_name,
}
), &[MatrixVersion::V1_0])
))
.await?;
*self.0.access_token.lock().unwrap() = Some(response.access_token.clone());
@ -158,10 +138,7 @@ impl<C: HttpClient> Client<C> {
&self,
) -> Result<register::v3::Response, Error<C::Error, ruma_client_api::uiaa::UiaaResponse>> {
let response = self
.send_request(
assign!(register::v3::Request::new(), { kind: RegistrationKind::Guest }),
&[MatrixVersion::V1_0],
)
.send_request(assign!(register::v3::Request::new(), { kind: RegistrationKind::Guest }))
.await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -182,10 +159,9 @@ impl<C: HttpClient> Client<C> {
password: &str,
) -> Result<register::v3::Response, Error<C::Error, ruma_client_api::uiaa::UiaaResponse>> {
let response = self
.send_request(
assign!(register::v3::Request::new(), { username, password: Some(password)}),
&[MatrixVersion::V1_0],
)
.send_request(assign!(register::v3::Request::new(), {
username, password: Some(password)
}))
.await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -200,13 +176,15 @@ impl<C: HttpClient> Client<C> {
/// ```no_run
/// use std::time::Duration;
///
/// # type MatrixClient = ruma_client::Client<ruma_client::http_client::Dummy>;
/// # use ruma_common::presence::PresenceState;
/// # use tokio_stream::{StreamExt as _};
/// # let homeserver_url = "https://example.com".parse().unwrap();
/// # let client = MatrixClient::new(homeserver_url, None);
/// # let next_batch_token = String::new();
/// # async {
/// # let client = ruma_client::Client::builder()
/// # .homeserver_url(homeserver_url)
/// # .build::<ruma_client::http_client::Dummy>()
/// # .await?;
/// # let next_batch_token = String::new();
/// let mut sync_stream = Box::pin(client.sync(
/// None,
/// next_batch_token,
@ -235,7 +213,7 @@ impl<C: HttpClient> Client<C> {
since: Some(&since),
set_presence,
timeout,
}), &[MatrixVersion::V1_0])
}))
.await?;
since = response.next_batch.clone();

View File

@ -0,0 +1,95 @@
use std::sync::{Arc, Mutex};
use ruma_api::{MatrixVersion, SendAccessToken};
use ruma_client_api::discover::get_supported_versions;
use super::{Client, ClientData};
use crate::{DefaultConstructibleHttpClient, Error, HttpClient, HttpClientExt};
/// A [`Client`] builder.
///
/// This type can be used to construct a `Client` through a few method calls.
pub struct ClientBuilder {
homeserver_url: Option<String>,
access_token: Option<String>,
supported_matrix_versions: Option<Vec<MatrixVersion>>,
}
impl ClientBuilder {
pub(super) fn new() -> Self {
Self { homeserver_url: None, access_token: None, supported_matrix_versions: None }
}
/// Set the homeserver URL.
///
/// The homeserver URL must be set before calling [`build()`][Self::build] or
/// [`http_client()`][Self::http_client].
pub fn homeserver_url(self, url: String) -> Self {
Self { homeserver_url: Some(url), ..self }
}
/// Set the access token.
pub fn access_token(self, access_token: Option<String>) -> Self {
Self { access_token, ..self }
}
/// Set the supported Matrix versions.
///
/// This method generally *shouldn't* be called. The [`build()`][Self::build] or
/// [`http_client()`][Self::http_client] method will take care of doing a
/// [`get_supported_versions`] request to find out about the supported versions.
pub fn supported_matrix_versions(self, versions: Vec<MatrixVersion>) -> Self {
Self { supported_matrix_versions: Some(versions), ..self }
}
/// Finish building the [`Client`].
///
/// Uses [`DefaultConstructibleHttpClient::default()`] to create an HTTP client instance.
/// Unless the supported Matrix versions were manually set via
/// [`supported_matrix_versions`][Self::supported_matrix_versions], this will do a
/// [`get_supported_versions`] request to find out about the supported versions.
pub async fn build<C>(self) -> Result<Client<C>, Error<C::Error, ruma_client_api::Error>>
where
C: DefaultConstructibleHttpClient,
{
self.http_client(C::default()).await
}
/// Set the HTTP client to finish building the [`Client`].
///
/// Unless the supported Matrix versions were manually set via
/// [`supported_matrix_versions`][Self::supported_matrix_versions], this will do a
/// [`get_supported_versions`] request to find out about the supported versions.
pub async fn http_client<C>(
self,
http_client: C,
) -> Result<Client<C>, Error<C::Error, ruma_client_api::Error>>
where
C: HttpClient,
{
let homeserver_url = self
.homeserver_url
.expect("homeserver URL has to be set prior to calling .build() or .http_client()");
let supported_matrix_versions = match self.supported_matrix_versions {
Some(versions) => versions,
None => http_client
.send_matrix_request(
&homeserver_url,
SendAccessToken::None,
&[MatrixVersion::V1_0],
get_supported_versions::Request::new(),
)
.await?
.known_versions()
.collect(),
};
Ok(Client(Arc::new(ClientData {
homeserver_url,
http_client,
access_token: Mutex::new(self.access_token),
supported_matrix_versions,
})))
}
}

View File

@ -10,11 +10,14 @@
//!
//! ```ignore
//! # // HACK: "ignore" the doctest here because client.log_in needs client-api feature.
//! // type MatrixClient = ruma_client::Client<ruma_client::http_client::_>;
//! # type MatrixClient = ruma_client::Client<ruma_client::http_client::Dummy>;
//! // type HttpClient = ruma_client::http_client::_;
//! # type HttpClient = ruma_client::http_client::Dummy;
//! # let work = async {
//! let homeserver_url = "https://example.com".parse().unwrap();
//! let client = MatrixClient::new(homeserver_url, None);
//! let client = ruma::Client::builder()
//! .homeserver_url(homeserver_url)
//! .build::<ruma_client::http_client::Dummy>()
//! .await?;
//!
//! let session = client
//! .log_in("@alice:example.com", "secret", None, None)
@ -31,14 +34,19 @@
//! application service that does not need to log in, but uses the access_token directly:
//!
//! ```no_run
//! # type MatrixClient = ruma_client::Client<ruma_client::http_client::Dummy>;
//! # type HttpClient = ruma_client::http_client::Dummy;
//! #
//! # async {
//! let homeserver_url = "https://example.com".parse().unwrap();
//! let client = ruma_client::Client::builder()
//! .homeserver_url(homeserver_url)
//! .access_token(Some("as_access_token".into()))
//! .build::<HttpClient>()
//! .await?;
//!
//! let work = async {
//! let homeserver_url = "https://example.com".parse().unwrap();
//! let client = MatrixClient::new(homeserver_url, Some("as_access_token".into()));
//!
//! // make calls to the API
//! };
//! // make calls to the API
//! # Result::<(), ruma_client::Error<_, _>>::Ok(())
//! # };
//! ```
//!
//! The `Client` type also provides methods for registering a new account if you don't already have
@ -51,27 +59,25 @@
//! For example:
//!
//! ```no_run
//! # type MatrixClient = ruma_client::Client<ruma_client::http_client::Dummy>;
//! # let homeserver_url = "https://example.com".parse().unwrap();
//! # let client = MatrixClient::new(homeserver_url, None);
//! # async {
//! # let client = ruma_client::Client::builder()
//! # .homeserver_url(homeserver_url)
//! # .build::<ruma_client::http_client::Dummy>()
//! # .await?;
//! use std::convert::TryFrom;
//!
//! use ruma_api::MatrixVersion;
//! use ruma_client_api::alias::get_alias;
//! use ruma_identifiers::{room_alias_id, room_id};
//!
//! async {
//! let response = client
//! .send_request(
//! get_alias::v3::Request::new(room_alias_id!("#example_room:example.com")),
//! &[MatrixVersion::V1_0],
//! )
//! .await?;
//! let response = client
//! .send_request(get_alias::v3::Request::new(room_alias_id!("#example_room:example.com")))
//! .await?;
//!
//! assert_eq!(response.room_id, room_id!("!n8f893n9:example.com"));
//! # Result::<(), ruma_client::Error<_, _>>::Ok(())
//! }
//! # ;
//! assert_eq!(response.room_id, room_id!("!n8f893n9:example.com"));
//! # Result::<(), ruma_client::Error<_, _>>::Ok(())
//! # };
//! ```
//!
//! # Crate features
@ -115,7 +121,7 @@ mod error;
pub mod http_client;
#[cfg(feature = "client-api")]
pub use self::client::Client;
pub use self::client::{Client, ClientBuilder};
pub use self::{
error::Error,
http_client::{DefaultConstructibleHttpClient, HttpClient, HttpClientExt},

View File

@ -10,8 +10,6 @@ use ruma::{
};
use ruma_api::MatrixVersion;
type MatrixClient = ruma::Client<ruma_client::http_client::Isahc>;
async fn hello_world(
homeserver_url: String,
username: &str,
@ -19,25 +17,18 @@ async fn hello_world(
room_alias: &RoomAliasId,
) -> anyhow::Result<()> {
let http_client = isahc::HttpClient::new()?;
let client = MatrixClient::with_http_client(http_client, homeserver_url, None);
let client =
ruma::Client::builder().homeserver_url(homeserver_url).http_client(http_client).await?;
client.log_in(username, password, None, Some("ruma-example-client")).await?;
let room_id = client
.send_request(get_alias::v3::Request::new(room_alias), &[MatrixVersion::V1_0])
.await?
.room_id;
let room_id = client.send_request(get_alias::v3::Request::new(room_alias)).await?.room_id;
client.send_request(join_room_by_id::v3::Request::new(&room_id)).await?;
client
.send_request(join_room_by_id::v3::Request::new(&room_id), &[MatrixVersion::V1_0])
.await?;
client
.send_request(
send_message_event::Request::new(
&room_id,
"1",
&RoomMessageEventContent::text_plain("Hello World!"),
)?,
&[MatrixVersion::V1_0],
)
.send_request(send_message_event::Request::new(
&room_id,
"1",
&RoomMessageEventContent::text_plain("Hello World!"),
)?)
.await?;
Ok(())

View File

@ -5,10 +5,9 @@ use ruma::{
events::room::message::RoomMessageEventContent,
RoomAliasId,
};
use ruma_api::MatrixVersion;
use ruma_identifiers::TransactionId;
type MatrixClient = ruma::Client<ruma_client::http_client::HyperNativeTls>;
type HttpClient = ruma::client::http_client::HyperNativeTls;
async fn hello_world(
homeserver_url: String,
@ -16,25 +15,18 @@ async fn hello_world(
password: &str,
room_alias: &RoomAliasId,
) -> anyhow::Result<()> {
let client = MatrixClient::new(homeserver_url, None);
let client =
ruma::Client::builder().homeserver_url(homeserver_url).build::<HttpClient>().await?;
client.log_in(username, password, None, Some("ruma-example-client")).await?;
let room_id = client
.send_request(get_alias::v3::Request::new(room_alias), &[MatrixVersion::V1_0])
.await?
.room_id;
let room_id = client.send_request(get_alias::v3::Request::new(room_alias)).await?.room_id;
client.send_request(join_room_by_id::v3::Request::new(&room_id)).await?;
client
.send_request(join_room_by_id::v3::Request::new(&room_id), &[MatrixVersion::V1_0])
.await?;
client
.send_request(
send_message_event::v3::Request::new(
&room_id,
&TransactionId::new(),
&RoomMessageEventContent::text_plain("Hello World!"),
)?,
&[MatrixVersion::V1_0],
)
.send_request(send_message_event::v3::Request::new(
&room_id,
&TransactionId::new(),
&RoomMessageEventContent::text_plain("Hello World!"),
)?)
.await?;
Ok(())

View File

@ -11,25 +11,23 @@ use ruma::{
};
use tokio_stream::StreamExt as _;
type MatrixClient = ruma::Client<ruma_client::http_client::HyperNativeTls>;
type HttpClient = ruma::client::http_client::HyperNativeTls;
async fn log_messages(
homeserver_url: String,
username: &str,
password: &str,
) -> anyhow::Result<()> {
let client = MatrixClient::new(homeserver_url, None);
let client =
ruma::Client::builder().homeserver_url(homeserver_url).build::<HttpClient>().await?;
client.log_in(username, password, None, None).await?;
let filter = FilterDefinition::ignore_all().into();
let initial_sync_response = client
.send_request(
assign!(sync_events::v3::Request::new(), {
filter: Some(&filter),
}),
&[ruma_api::MatrixVersion::V1_0],
)
.send_request(assign!(sync_events::v3::Request::new(), {
filter: Some(&filter),
}))
.await?;
let mut sync_stream = Box::pin(client.sync(

View File

@ -2,12 +2,9 @@ use std::{convert::TryInto, error::Error, io, process::exit, time::Duration};
use futures_util::future::{join, join_all};
use ruma::{
api::{
client::{
filter::FilterDefinition, membership::join_room_by_id, message::send_message_event,
sync::sync_events,
},
MatrixVersion,
api::client::{
filter::FilterDefinition, membership::join_room_by_id, message::send_message_event,
sync::sync_events,
},
assign, client,
events::{
@ -41,11 +38,11 @@ async fn run() -> Result<(), Box<dyn Error>> {
let http_client =
hyper::Client::builder().build::<_, hyper::Body>(hyper_tls::HttpsConnector::new());
let matrix_client = if let Some(state) = read_state().await.ok().flatten() {
MatrixClient::with_http_client(
http_client.clone(),
config.homeserver.to_owned(),
Some(state.access_token),
)
ruma::Client::builder()
.homeserver_url(config.homeserver.clone())
.access_token(Some(state.access_token))
.http_client(http_client.clone())
.await?
} else if config.password.is_some() {
let client = create_matrix_session(http_client.clone(), &config).await?;
@ -63,12 +60,9 @@ async fn run() -> Result<(), Box<dyn Error>> {
let filter = FilterDefinition::ignore_all().into();
let initial_sync_response = matrix_client
.send_request(
assign!(sync_events::v3::Request::new(), {
filter: Some(&filter),
}),
&[MatrixVersion::V1_0],
)
.send_request(assign!(sync_events::v3::Request::new(), {
filter: Some(&filter),
}))
.await?;
let user_id = &config.username;
let not_senders = &[user_id.clone()];
@ -121,8 +115,11 @@ async fn create_matrix_session(
config: &Config,
) -> Result<MatrixClient, Box<dyn Error>> {
if let Some(password) = &config.password {
let client =
MatrixClient::with_http_client(http_client, config.homeserver.to_owned(), None);
let client = ruma::Client::builder()
.homeserver_url(config.homeserver.clone())
.http_client(http_client)
.await?;
if let Err(e) = client.log_in(config.username.as_ref(), password, None, None).await {
let reason = match e {
client::Error::AuthenticationRequired => "invalid credentials specified".to_owned(),
@ -136,6 +133,7 @@ async fn create_matrix_session(
};
return Err(format!("Failed to log in: {}", reason).into());
}
Ok(client)
} else {
Err("Failed to create session: no password stored in config".to_owned().into())
@ -167,10 +165,11 @@ async fn handle_message(
let txn_id = TransactionId::new();
let req = send_message_event::v3::Request::new(room_id, &txn_id, &joke_content)?;
// Do nothing if we can't send the message.
let _ = matrix_client.send_request(req, &[MatrixVersion::V1_0]).await;
let _ = matrix_client.send_request(req).await;
}
}
}
Ok(())
}
@ -180,16 +179,14 @@ async fn handle_invitations(
room_id: &RoomId,
) -> Result<(), Box<dyn Error>> {
println!("invited to {}", &room_id);
matrix_client
.send_request(join_room_by_id::v3::Request::new(room_id), &[MatrixVersion::V1_0])
.await?;
matrix_client.send_request(join_room_by_id::v3::Request::new(room_id)).await?;
let greeting = "Hello! My name is Mr. Bot! I like to tell jokes. Like this one: ";
let joke = get_joke(http_client).await.unwrap_or_else(|_| "err... never mind.".to_owned());
let content = RoomMessageEventContent::text_plain(format!("{}\n{}", greeting, joke));
let txn_id = TransactionId::new();
let message = send_message_event::v3::Request::new(room_id, &txn_id, &content)?;
matrix_client.send_request(message, &[MatrixVersion::V1_0]).await?;
matrix_client.send_request(message).await?;
Ok(())
}