7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 1 | use std::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 2 | future::Future, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 3 | ops::Deref, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 4 | option_env, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 5 | path::{Path, PathBuf}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 6 | sync::{Arc, LazyLock, Mutex as StdMutex}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 7 | time::Duration, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 8 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 9 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 10 | use anyhow::Result; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 11 | use assign::assign; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 12 | use matrix_sdk::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 13 | Client, ClientBuilder, Room, ThreadingSupport, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 14 | config::{RequestConfig, SyncSettings}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 15 | encryption::EncryptionSettings, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 16 | ruma::{ |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 17 | RoomId, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 18 | api::client::{account::register::v3::Request as RegistrationRequest, uiaa}, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 19 | time::Instant, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 20 | }, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 21 | sliding_sync::VersionBuilder, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 22 | sync::SyncResponse, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 23 | timeout::ElapsedError, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 24 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 25 | use matrix_sdk_base::crypto::{CollectStrategy, DecryptionSettings, TrustRequirement}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 26 | use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 27 | use rand::RngExt; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 28 | use tempfile::{TempDir, tempdir}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 29 | use tokio::{sync::Mutex, time::sleep}; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 30 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 31 | /// This global maintains temp directories alive for the whole lifetime of the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 32 | /// process. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 33 | static TMP_DIRS: LazyLock<Mutex<Vec<TempDir>>> = LazyLock::new(Mutex::default); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 34 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 35 | enum SqlitePath { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 36 | Random, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 37 | Path(PathBuf), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 38 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 39 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 40 | pub struct TestClientBuilder { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 41 | username: String, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 42 | use_sqlite_dir: Option<SqlitePath>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 43 | decryption_settings: Option<DecryptionSettings>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 44 | room_key_recipient_strategy: CollectStrategy, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 45 | encryption_settings: EncryptionSettings, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 46 | enable_share_history_on_invite: bool, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 47 | threading_support: ThreadingSupport, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 48 | http_proxy: Option<String>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 49 | cross_process_lock_config: CrossProcessLockConfig, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 50 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 51 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 52 | impl TestClientBuilder { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 53 | pub fn new(username: impl AsRef<str>) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 54 | let suffix: u128 = rand::rng().random(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 55 | let randomized_username = format!("{}{}", username.as_ref(), suffix); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 56 | Self::with_exact_username(randomized_username) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 57 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 58 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 59 | pub fn with_exact_username(username: String) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 60 | Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 61 | username, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 62 | use_sqlite_dir: None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 63 | decryption_settings: None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 64 | encryption_settings: Default::default(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 65 | room_key_recipient_strategy: Default::default(), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 66 | enable_share_history_on_invite: false, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 67 | threading_support: ThreadingSupport::Disabled, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 68 | http_proxy: None, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 69 | cross_process_lock_config: CrossProcessLockConfig::SingleProcess, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 70 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 71 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 72 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 73 | pub fn use_sqlite(mut self) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 74 | self.use_sqlite_dir = Some(SqlitePath::Random); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 75 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 76 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 77 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 78 | /// Create or re-use a Sqlite store (with no passphrase) in the supplied |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 79 | /// directory. Note: this path must remain valid throughout the use of |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 80 | /// the constructed Client, so if you created a TempDir you must hang on |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 81 | /// to a reference to it throughout the test. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 82 | pub fn use_sqlite_dir(mut self, path: &Path) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 83 | self.use_sqlite_dir = Some(SqlitePath::Path(path.to_owned())); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 84 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 85 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 86 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 87 | pub fn encryption_settings(mut self, encryption_settings: EncryptionSettings) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 88 | self.encryption_settings = encryption_settings; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 89 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 90 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 91 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 92 | pub fn enable_share_history_on_invite(mut self, enable_share_history_on_invite: bool) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 93 | self.enable_share_history_on_invite = enable_share_history_on_invite; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 94 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 95 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 96 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 97 | pub fn enable_threading_support(mut self, thread_support: ThreadingSupport) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 98 | self.threading_support = thread_support; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 99 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 100 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 101 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 102 | /// Simulate the behaviour of the clients when the "exclude insecure |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 103 | /// devices" (MSC4153) labs flag is enabled. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 104 | pub fn exclude_insecure_devices(mut self, exclude_insecure_devices: bool) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 105 | let (sender_device_trust_requirement, room_key_recipient_strategy) = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 106 | if exclude_insecure_devices { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 107 | (TrustRequirement::CrossSignedOrLegacy, CollectStrategy::IdentityBasedStrategy) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 108 | } else { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 109 | (TrustRequirement::Untrusted, CollectStrategy::AllDevices) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 110 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 111 | self.decryption_settings = Some(DecryptionSettings { sender_device_trust_requirement }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 112 | self.room_key_recipient_strategy = room_key_recipient_strategy; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 113 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 114 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 115 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 116 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 117 | pub fn cross_process_lock_config( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 118 | mut self, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 119 | cross_process_lock_config: CrossProcessLockConfig, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 120 | ) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 121 | self.cross_process_lock_config = cross_process_lock_config; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 122 | self |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 123 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 124 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 125 | fn common_client_builder(&self) -> ClientBuilder { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 126 | let homeserver_url = |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 127 | option_env!("HOMESERVER_URL").unwrap_or("http://localhost:8228").to_owned(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 128 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 129 | let mut client_builder = Client::builder() |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 130 | .user_agent("matrix-sdk-integration-tests") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 131 | .homeserver_url(homeserver_url) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 132 | .sliding_sync_version_builder(VersionBuilder::Native) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 133 | .with_encryption_settings(self.encryption_settings) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 134 | .with_room_key_recipient_strategy(self.room_key_recipient_strategy.clone()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 135 | .with_enable_share_history_on_invite(self.enable_share_history_on_invite) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 136 | .with_threading_support(self.threading_support) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 137 | .request_config(RequestConfig::short_retry()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 138 | .cross_process_store_config(self.cross_process_lock_config.clone()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 139 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 140 | if let Some(decryption_settings) = &self.decryption_settings { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 141 | client_builder = client_builder.with_decryption_settings(decryption_settings.clone()) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 142 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 143 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 144 | if let Some(proxy) = &self.http_proxy { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 145 | client_builder = client_builder.proxy(proxy); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 146 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 147 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 148 | client_builder |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 149 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 150 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 151 | /// Create a new Client that is a copy of the supplied one, created using |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 152 | /// [`Client::restore_session`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 153 | pub async fn duplicate(self, other: &Client) -> Result<Client> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 154 | let client_builder = self.common_client_builder(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 155 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 156 | let client = match self.use_sqlite_dir { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 157 | Some(SqlitePath::Path(path_buf)) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 158 | client_builder.sqlite_store(&path_buf, None).build().await? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 159 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 160 | _ => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 161 | panic!("You must call use_sqlite_dir for a duplicate client!"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 162 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 163 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 164 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 165 | client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 166 | .restore_session( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 167 | other.session().expect("Session must be logged in before we can duplicate it"), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 168 | ) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 169 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 170 | .expect("Failed to restore session"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 171 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 172 | Ok(client) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 173 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 174 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 175 | pub async fn build(self) -> Result<Client> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 176 | let client_builder = self.common_client_builder(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 177 | let client = match self.use_sqlite_dir { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 178 | None => client_builder.build().await?, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 179 | Some(SqlitePath::Random) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 180 | let tmp_dir = tempdir()?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 181 | let client = client_builder.sqlite_store(tmp_dir.path(), None).build().await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 182 | TMP_DIRS.lock().await.push(tmp_dir); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 183 | client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 184 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 185 | Some(SqlitePath::Path(path_buf)) => { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 186 | client_builder.sqlite_store(&path_buf, None).build().await? |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 187 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 188 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 189 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 190 | // safe to assume we have not registered this user yet, but ignore if we did |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 191 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 192 | let auth = client.matrix_auth(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 193 | let mut try_login = true; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 194 | if let Err(resp) = auth.register(RegistrationRequest::new()).await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 195 | // FIXME: do actually check the registration types... |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 196 | if let Some(_response) = resp.as_uiaa_response() { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 197 | let request = assign!(RegistrationRequest::new(), { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 198 | username: Some(self.username.clone()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 199 | password: Some(self.username.clone()), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 200 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 201 | auth: Some(uiaa::AuthData::Dummy(uiaa::Dummy::new())), |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 202 | }); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 203 | // if this failed, we will attempt to login after. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 204 | try_login = auth.register(request).await.is_err(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 205 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 206 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 207 | if try_login { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 208 | auth.login_username(&self.username, &self.username).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 209 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 210 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 211 | Ok(client) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 212 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 213 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 214 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 215 | /// Client that correctly maintains and propagates sync token values. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 216 | #[derive(Clone)] |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 217 | pub struct SyncTokenAwareClient { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 218 | client: Client, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 219 | token: Arc<StdMutex<Option<String>>>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 220 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 221 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 222 | impl SyncTokenAwareClient { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 223 | pub fn new(client: Client) -> Self { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 224 | Self { client, token: Arc::new(None.into()) } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 225 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 226 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 227 | pub async fn sync_once(&self) -> Result<SyncResponse> { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 228 | let mut settings = SyncSettings::default().timeout(Duration::from_secs(1)); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 229 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 230 | let token = { self.token.lock().unwrap().clone() }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 231 | if let Some(token) = token { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 232 | settings = settings.token(token); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 233 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 234 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 235 | let response = self.client.sync_once(settings).await?; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 236 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 237 | let mut prev_token = self.token.lock().unwrap(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 238 | if prev_token.as_ref() != Some(&response.next_batch) { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 239 | *prev_token = Some(response.next_batch.clone()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 240 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 241 | Ok(response) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 242 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 243 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 244 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 245 | impl Deref for SyncTokenAwareClient { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 246 | type Target = Client; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 247 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 248 | fn deref(&self) -> &Self::Target { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 249 | &self.client |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 250 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 251 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 252 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 253 | /// Repeatedly run the given callback, at increasing intervals, until it returns |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 254 | /// `Some`, or the given timeout has elapsed. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 255 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 256 | /// The callback `cb` is called with the attempt number as an argument. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 257 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 258 | /// # Returns |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 259 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 260 | /// The result of `cb` if it returned `Some`. If the timeout elapses, an error |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 261 | /// of type [`ElapsedError`]. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 262 | pub async fn wait_until_some<F, Fut, R>( |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 263 | mut cb: F, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 264 | timeout_duration: Duration, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 265 | ) -> Result<R, ElapsedError> |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 266 | where |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 267 | F: FnMut(u32) -> Fut, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 268 | Fut: Future<Output = Option<R>>, |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 269 | { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 270 | let start = Instant::now(); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 271 | let mut attempt_count = 1; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 272 | loop { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 273 | if let Some(r) = cb(attempt_count).await { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 274 | return Ok(r); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 275 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 276 | if start - Instant::now() > timeout_duration { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 277 | return Err(ElapsedError()); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 278 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 279 | sleep(Duration::from_millis(125) * attempt_count).await; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 280 | attempt_count += 1; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 281 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 282 | } |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 283 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 284 | /// Waits for a room to arrive from a sync, for ~2 seconds. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 285 | /// |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 286 | /// Note: this doesn't run any sync, it assumes a sync has been running in the |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 287 | /// background. |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 288 | pub async fn wait_for_room(client: &Client, room_id: &RoomId) -> Room { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 289 | let cb = async |i| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 290 | client.get_room(room_id).or_else(|| { |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 291 | tracing::warn!("attempt #{i}, alice couldn't see the room, retrying in a few…"); |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 292 | None |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 293 | }) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 294 | }; |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 295 | |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 296 | wait_until_some(cb, Duration::from_secs(2)) |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 297 | .await |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 298 | .expect("room couldn't be fetched after all attempts") |
7ff8adafe9e4 | atagen | 2026-10-22 12:30 UTC | 299 | } |