matrix_sdk/client/
futures.rs1#![deny(unreachable_pub)]
16
17use std::{fmt::Debug, future::IntoFuture};
18
19use eyeball::{SharedObservable, Subscriber};
20use js_int::UInt;
21use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm, boxed_into_future};
22use oauth2::{RequestTokenError, basic::BasicErrorResponseType};
23use ruma::api::{
24 OutgoingRequest,
25 client::media,
26 error::{ErrorKind, FromHttpResponseError},
27 path_builder::PathBuilder,
28};
29use tracing::{error, trace};
30
31use super::super::Client;
32use crate::{
33 Error, RefreshTokenError, TransmissionProgress,
34 authentication::oauth::OAuthError,
35 config::RequestConfig,
36 error::{HttpError, HttpResult},
37 http_client::{SupportedAuthScheme, SupportedPathBuilder},
38 media::MediaError,
39};
40
41#[allow(missing_debug_implementations)]
43pub struct SendRequest<R> {
44 pub(crate) client: Client,
45 pub(crate) request: R,
46 pub(crate) config: Option<RequestConfig>,
47 pub(crate) send_progress: SharedObservable<TransmissionProgress>,
48}
49
50impl<R> SendRequest<R> {
51 pub fn with_send_progress_observable(
58 mut self,
59 send_progress: SharedObservable<TransmissionProgress>,
60 ) -> Self {
61 self.send_progress = send_progress;
62 self
63 }
64
65 pub fn with_request_config(mut self, request_config: impl Into<Option<RequestConfig>>) -> Self {
68 self.config = request_config.into();
69 self
70 }
71
72 pub fn subscribe_to_send_progress(&self) -> Subscriber<TransmissionProgress> {
74 self.send_progress.subscribe()
75 }
76}
77
78impl<R> IntoFuture for SendRequest<R>
79where
80 R: OutgoingRequest + Clone + Debug + SendOutsideWasm + SyncOutsideWasm + 'static,
81 R::Authentication: SupportedAuthScheme,
82 R::PathBuilder: SupportedPathBuilder,
83 for<'a> <R::PathBuilder as PathBuilder>::Input<'a>: SendOutsideWasm + SyncOutsideWasm,
84 R::IncomingResponse: SendOutsideWasm + SyncOutsideWasm,
85 HttpError: From<FromHttpResponseError<R::EndpointError>>,
86{
87 type Output = HttpResult<R::IncomingResponse>;
88 boxed_into_future!();
89
90 fn into_future(self) -> Self::IntoFuture {
91 enum RetryRequest {
92 Yes,
93 No,
94 }
95
96 async fn handle_unknown_token_error(
99 e: &HttpError,
100 client: &Client,
101 ) -> HttpResult<RetryRequest> {
102 let Some(ErrorKind::UnknownToken(unknown_token_data)) = e.client_api_error_kind()
105 else {
106 return Ok(RetryRequest::No);
107 };
108
109 trace!("Token refresh: Unknown token error received.");
110
111 if !client.inner.auth_ctx.handle_refresh_tokens {
114 trace!("Token refresh: Automatic refresh disabled.");
115 client.broadcast_unknown_token(unknown_token_data);
116 return Ok(RetryRequest::No);
117 }
118
119 if let Err(refresh_error) = client.refresh_access_token().await {
121 match &refresh_error {
122 RefreshTokenError::RefreshTokenRequired => {
123 trace!("Token refresh: The session doesn't have a refresh token.");
124 client.broadcast_unknown_token(unknown_token_data);
127 Ok(RetryRequest::No)
128 }
129
130 RefreshTokenError::OAuth(oauth_error) => {
131 match &**oauth_error {
132 OAuthError::RefreshToken(RequestTokenError::ServerResponse(
133 error_response,
134 )) if *error_response.error()
135 == BasicErrorResponseType::InvalidGrant =>
136 {
137 error!(
138 "Token refresh: OAuth 2.0 refresh_token rejected \
139 with invalid grant"
140 );
141 client.broadcast_unknown_token(unknown_token_data);
144 }
145 _ => {
146 trace!("Token refresh: OAuth 2.0 refresh encountered a problem.");
147 }
150 }
151 Err(HttpError::RefreshToken(refresh_error))
152 }
153
154 RefreshTokenError::MatrixAuth(http_error) => {
155 let rejected = http_error
159 .as_client_api_error()
160 .is_some_and(|error| !error.status_code.is_server_error());
161
162 if rejected {
163 error!("Token refresh: the homeserver rejected the refresh token");
164 client.broadcast_unknown_token(unknown_token_data);
165 } else {
166 trace!("Token refresh: the refresh request itself failed.");
167 }
168 Err(HttpError::RefreshToken(refresh_error))
169 }
170 }
171 } else {
172 trace!("Token refresh: Refresh succeeded, retrying request.");
173 Ok(RetryRequest::Yes)
174 }
175 }
176
177 let Self { client, request, config, send_progress } = self;
178
179 Box::pin(async move {
180 let res =
181 Box::pin(client.send_inner(request.clone(), config, send_progress.clone())).await;
182
183 if let Err(e) = &res
184 && let RetryRequest::Yes = handle_unknown_token_error(e, &client).await?
185 {
186 return Box::pin(client.send_inner(request, config, send_progress)).await;
187 }
188
189 res
190 })
191 }
192}
193
194#[allow(missing_debug_implementations)]
198pub struct SendMediaUploadRequest {
199 send_request: SendRequest<media::create_content::v3::Request>,
200}
201
202impl SendMediaUploadRequest {
203 pub fn new(request: SendRequest<media::create_content::v3::Request>) -> Self {
204 Self { send_request: request }
205 }
206
207 pub fn with_send_progress_observable(
214 mut self,
215 send_progress: SharedObservable<TransmissionProgress>,
216 ) -> Self {
217 self.send_request = self.send_request.with_send_progress_observable(send_progress);
218 self
219 }
220
221 pub fn subscribe_to_send_progress(&self) -> Subscriber<TransmissionProgress> {
223 self.send_request.send_progress.subscribe()
224 }
225}
226
227impl IntoFuture for SendMediaUploadRequest {
228 type Output = Result<media::create_content::v3::Response, Error>;
229 boxed_into_future!();
230
231 fn into_future(self) -> Self::IntoFuture {
232 let request_length = self.send_request.request.file.len();
233 let client = self.send_request.client.clone();
234 let send_request = self.send_request;
235
236 Box::pin(async move {
237 let max_upload_size = client.load_or_fetch_max_upload_size().await?;
238 let request_length = UInt::new_wrapping(request_length as u64);
239 if request_length > max_upload_size {
240 return Err(Error::Media(MediaError::MediaTooLargeToUpload {
241 max: max_upload_size,
242 current: request_length,
243 }));
244 }
245
246 send_request.into_future().await.map_err(Into::into)
247 })
248 }
249}