1use std::{
2 borrow::Cow,
3 sync::{Arc, Mutex},
4 time::Duration,
5};
6
7use assign::assign;
8use async_stream::try_stream;
9use futures_core::stream::Stream;
10use ruma::{
11 DeviceId,
12 api::{
13 AppserviceUserIdentity, OutgoingRequest, SupportedVersions,
14 auth_scheme::{AuthScheme, SendAccessToken},
15 client::{
16 account::register::{self, RegistrationKind},
17 session::login::{self, v3::LoginInfo},
18 sync::sync_events,
19 uiaa::{MatrixUserIdentifier, UserIdentifier},
20 },
21 path_builder::{PathBuilder, SinglePath, VersionHistory},
22 },
23 presence::PresenceState,
24};
25
26use crate::{Error, HttpClient, ResponseError, ResponseResult, send_customized_request};
27
28mod builder;
29
30pub use self::builder::ClientBuilder;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
34#[non_exhaustive]
35pub enum TokenMode {
36 #[default]
37 SendIfRequired,
39
40 SendAlways,
42
43 AppService,
45}
46
47#[derive(Clone, Debug)]
49pub struct Client<C>(Arc<ClientData<C>>);
50
51#[derive(Debug)]
53struct ClientData<C> {
54 homeserver_url: String,
56
57 http_client: C,
59
60 access_token: Mutex<Option<String>>,
62
63 token_mode: TokenMode,
65
66 supported_matrix_versions: SupportedVersions,
68}
69
70impl Client<()> {
71 pub fn builder() -> ClientBuilder {
73 ClientBuilder::new()
74 }
75}
76
77impl<C> Client<C> {
78 pub fn access_token(&self) -> Option<String> {
82 self.0.access_token.lock().expect("session mutex was poisoned").clone()
83 }
84}
85
86impl<C: HttpClient> Client<C> {
87 pub async fn send_request<R>(&self, request: R) -> ResponseResult<C, R>
89 where
90 R: OutgoingRequest,
91 for<'a> R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
92 R::PathBuilder: SupportedPathBuilder,
93 {
94 self.send_customized_request(request, |_| Ok(())).await
95 }
96
97 pub async fn send_customized_request<R, F>(
99 &self,
100 request: R,
101 customize: F,
102 ) -> ResponseResult<C, R>
103 where
104 R: OutgoingRequest,
105 for<'a> R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
106 R::PathBuilder: SupportedPathBuilder,
107 F: FnOnce(&mut http::Request<C::RequestBody>) -> Result<(), ResponseError<C, R>>,
108 {
109 let token_mode = self.0.token_mode;
110 let access_token = self.access_token();
111
112 let send_access_token = match (token_mode, access_token.as_deref()) {
113 (TokenMode::AppService, Some(at)) => SendAccessToken::Appservice(at),
114 (TokenMode::SendIfRequired, Some(at)) => SendAccessToken::IfRequired(at),
115 (TokenMode::SendAlways, Some(at)) => SendAccessToken::Always(at),
116 (_, None) => SendAccessToken::None,
117 };
118
119 send_customized_request(
120 &self.0.http_client,
121 &self.0.homeserver_url,
122 send_access_token,
123 R::PathBuilder::get_path_builder_input(self),
124 request,
125 customize,
126 )
127 .await
128 }
129
130 pub async fn send_request_as<R>(
135 &self,
136 identity: AppserviceUserIdentity<'_>,
137 request: R,
138 ) -> ResponseResult<C, R>
139 where
140 R: OutgoingRequest,
141 for<'a> R::Authentication: AuthScheme<Input<'a> = SendAccessToken<'a>>,
142 R::PathBuilder: SupportedPathBuilder,
143 {
144 self.send_customized_request(request, |request| {
145 Ok(identity.maybe_add_to_uri(request.uri_mut())?)
146 })
147 .await
148 }
149
150 pub async fn log_in(
155 &self,
156 user: &str,
157 password: &str,
158 device_id: Option<&DeviceId>,
159 initial_device_display_name: Option<&str>,
160 ) -> Result<login::v3::Response, Error<C::Error, ruma::api::error::Error>> {
161 let login_info = LoginInfo::Password(login::v3::Password::new(
162 UserIdentifier::Matrix(MatrixUserIdentifier::new(user.to_owned())),
163 password.to_owned(),
164 ));
165 let response = self
166 .send_request(assign!(login::v3::Request::new(login_info), {
167 device_id: device_id.map(ToOwned::to_owned),
168 initial_device_display_name: initial_device_display_name.map(ToOwned::to_owned),
169 }))
170 .await?;
171
172 *self.0.access_token.lock().unwrap() = Some(response.access_token.clone());
173
174 Ok(response)
175 }
176
177 pub async fn register_guest(
182 &self,
183 ) -> Result<register::v3::Response, Error<C::Error, ruma::api::client::uiaa::UiaaResponse>>
184 {
185 let response = self
186 .send_request(assign!(register::v3::Request::new(), { kind: RegistrationKind::Guest }))
187 .await?;
188
189 self.0.access_token.lock().unwrap().clone_from(&response.access_token);
190
191 Ok(response)
192 }
193
194 pub async fn register_user(
202 &self,
203 username: Option<&str>,
204 password: &str,
205 ) -> Result<register::v3::Response, Error<C::Error, ruma::api::client::uiaa::UiaaResponse>>
206 {
207 let response = self
208 .send_request(assign!(register::v3::Request::new(), {
209 username: username.map(ToOwned::to_owned),
210 password: Some(password.to_owned())
211 }))
212 .await?;
213
214 self.0.access_token.lock().unwrap().clone_from(&response.access_token);
215
216 Ok(response)
217 }
218
219 pub fn sync(
248 &self,
249 filter: Option<sync_events::v3::Filter>,
250 mut since: String,
251 set_presence: PresenceState,
252 timeout: Option<Duration>,
253 ) -> impl Stream<
254 Item = Result<sync_events::v3::Response, Error<C::Error, ruma::api::error::Error>>,
255 > + '_ {
256 try_stream! {
257 loop {
258 let response = self
259 .send_request(assign!(sync_events::v3::Request::new(), {
260 filter: filter.clone(),
261 since: Some(since.clone()),
262 set_presence: set_presence.clone(),
263 timeout,
264 }))
265 .await?;
266
267 since.clone_from(&response.next_batch);
268 yield response;
269 }
270 }
271 }
272}
273
274pub trait SupportedPathBuilder: PathBuilder {
278 fn get_path_builder_input<C>(client: &Client<C>) -> Self::Input<'_>;
280}
281
282impl SupportedPathBuilder for VersionHistory {
283 fn get_path_builder_input<C>(client: &Client<C>) -> Self::Input<'_> {
284 Cow::Borrowed(&client.0.supported_matrix_versions)
285 }
286}
287
288impl SupportedPathBuilder for SinglePath {
289 fn get_path_builder_input<C>(_client: &Client<C>) -> Self::Input<'_> {}
290}