-
Notifications
You must be signed in to change notification settings - Fork 270
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use ClientBuilder pattern in SDK FFI #739
Changes from 13 commits
a9221c8
943ba8d
3cfaab8
184328a
f72a93d
6c23a69
f239c9d
1f6cd44
1d39e15
c155c61
79be457
ed8a69c
55b3710
0aa33df
a7c9dda
3f970ad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ use matrix_sdk::{ | |
sync::sync_events::v3::Filter, | ||
}, | ||
events::room::MediaSource, | ||
TransactionId, | ||
TransactionId, UserId, | ||
}, | ||
Client as MatrixClient, LoopCtrl, | ||
}; | ||
|
@@ -44,6 +44,23 @@ impl Client { | |
} | ||
} | ||
|
||
pub fn login(&self, username: String, password: String) -> anyhow::Result<()> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are the methods that used to exist as top-level functions, now methods on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙌 |
||
RUNTIME.block_on(async move { | ||
self.client.login_username(&username, &password).send().await?; | ||
Ok(()) | ||
}) | ||
} | ||
|
||
pub fn restore_login(&self, restore_token: String) -> anyhow::Result<()> { | ||
let RestoreToken { session, homeurl: _, is_guest: _ } = | ||
serde_json::from_str(&restore_token)?; | ||
|
||
RUNTIME.block_on(async move { | ||
self.client.restore_login(session).await?; | ||
Ok(()) | ||
}) | ||
} | ||
|
||
pub fn set_delegate(&self, delegate: Option<Box<dyn ClientDelegate>>) { | ||
*self.delegate.write() = delegate; | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,87 @@ | ||||||
use std::{fs, path::PathBuf, sync::Arc}; | ||||||
|
||||||
use anyhow::Context; | ||||||
use matrix_sdk::{ | ||||||
ruma::UserId, store::make_store_config, Client as MatrixClient, | ||||||
ClientBuilder as MatrixClientBuilder, | ||||||
}; | ||||||
use sanitize_filename_reader_friendly::sanitize; | ||||||
|
||||||
use super::{client::Client, ClientState, RUNTIME}; | ||||||
|
||||||
#[derive(Clone)] | ||||||
pub struct ClientBuilder { | ||||||
base_path: Option<String>, | ||||||
username: Option<String>, | ||||||
homeserver_url: Option<String>, | ||||||
inner: MatrixClientBuilder, | ||||||
} | ||||||
|
||||||
impl ClientBuilder { | ||||||
pub fn new() -> Self { | ||||||
Self { | ||||||
base_path: None, | ||||||
username: None, | ||||||
homeserver_url: None, | ||||||
inner: MatrixClient::builder().user_agent("rust-sdk-ios"), | ||||||
} | ||||||
} | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Docs should probably say that base path must be set. Which let's me wonder if we shouldn't have that as a parameters on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think Andy actually wanted to have it as a However, this point only applies to Kotlin since UniFFI's Swift codegen requires callers to re-state argument names (as in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea I generally favor API that makes invalid states impossible to represent, which would require passing them to |
||||||
pub fn base_path(self: Arc<Self>, path: String) -> Arc<Self> { | ||||||
let mut builder = unwrap_or_clone_arc(self); | ||||||
builder.base_path = Some(path); | ||||||
Arc::new(builder) | ||||||
} | ||||||
|
||||||
pub fn username(self: Arc<Self>, username: String) -> Arc<Self> { | ||||||
let mut builder = unwrap_or_clone_arc(self); | ||||||
builder.username = Some(username); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why hold an inner builder but still have all parameters twice? Why not just
Suggested change
For the homeserver, too... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can't currently read those fields from the inner builder, but they're needed in |
||||||
Arc::new(builder) | ||||||
} | ||||||
|
||||||
pub fn homeserver_url(self: Arc<Self>, url: String) -> Arc<Self> { | ||||||
let mut builder = unwrap_or_clone_arc(self); | ||||||
builder.homeserver_url = Some(url); | ||||||
Arc::new(builder) | ||||||
} | ||||||
|
||||||
pub fn build(self: Arc<Self>) -> anyhow::Result<Arc<Client>> { | ||||||
let builder = unwrap_or_clone_arc(self); | ||||||
|
||||||
let base_path = builder.base_path.context("Base path was not set")?; | ||||||
let username = builder | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would fail for guest clients... but as this also drops support fir them, I guess that's fine... |
||||||
.username | ||||||
.context("Username to determine homeserver and home path was not set")?; | ||||||
|
||||||
// Determine store path | ||||||
let data_path = PathBuf::from(base_path).join(sanitize(&username)); | ||||||
fs::create_dir_all(&data_path)?; | ||||||
let store_config = make_store_config(&data_path, None)?; | ||||||
|
||||||
let mut inner_builder = builder.inner.store_config(store_config); | ||||||
|
||||||
// Determine server either from explicitly set homeserver or from userId | ||||||
if let Some(homeserver_url) = builder.homeserver_url { | ||||||
inner_builder = inner_builder.homeserver_url(homeserver_url); | ||||||
} else { | ||||||
let user = UserId::parse(username)?; | ||||||
inner_builder = inner_builder.server_name(user.server_name()); | ||||||
} | ||||||
|
||||||
RUNTIME.block_on(async move { | ||||||
let client = inner_builder.build().await?; | ||||||
let c = Client::new(client, ClientState::default()); | ||||||
Ok(Arc::new(c)) | ||||||
}) | ||||||
} | ||||||
} | ||||||
|
||||||
impl Default for ClientBuilder { | ||||||
fn default() -> Self { | ||||||
Self::new() | ||||||
} | ||||||
} | ||||||
Comment on lines
+79
to
+83
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's pretty useless in this context but whatever. |
||||||
|
||||||
fn unwrap_or_clone_arc<T: Clone>(arc: Arc<T>) -> T { | ||||||
Arc::try_unwrap(arc).unwrap_or_else(|x| (*x).clone()) | ||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,16 +4,15 @@ | |
|
||
pub mod backward_stream; | ||
pub mod client; | ||
pub mod client_builder; | ||
pub mod messages; | ||
pub mod room; | ||
mod uniffi_api; | ||
|
||
use std::{fs, path, sync::Arc}; | ||
|
||
use client::Client; | ||
use matrix_sdk::{store::make_store_config, Client as MatrixClient, ClientBuilder, Session}; | ||
use client_builder::ClientBuilder; | ||
use matrix_sdk::Session; | ||
use once_cell::sync::Lazy; | ||
use sanitize_filename_reader_friendly::sanitize; | ||
use serde::{Deserialize, Serialize}; | ||
use tokio::runtime::Runtime; | ||
pub use uniffi_api::*; | ||
|
@@ -25,63 +24,6 @@ pub use matrix_sdk::ruma::{api::client::account::register, UserId}; | |
|
||
pub use self::{backward_stream::*, client::*, messages::*, room::*}; | ||
|
||
pub fn guest_client(base_path: String, homeurl: String) -> anyhow::Result<Arc<Client>> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The guest client login is not used by element-x, only in a unit test of sample project that could be recreated in other ways, so I did not port this method yet. If I missed some other reason why this is important, I can bring it back |
||
let builder = new_client_builder(base_path, homeurl.clone())?.homeserver_url(&homeurl); | ||
let mut guest_registration = register::v3::Request::new(); | ||
guest_registration.kind = register::RegistrationKind::Guest; | ||
RUNTIME.block_on(async move { | ||
let client = builder.build().await?; | ||
let register = client.register(guest_registration).await?; | ||
let session = Session { | ||
access_token: register.access_token.expect("no access token given"), | ||
user_id: register.user_id, | ||
device_id: register.device_id.clone().expect("device ID is given by server"), | ||
}; | ||
client.restore_login(session).await?; | ||
let c = Client::new(client, ClientState { is_guest: true, ..ClientState::default() }); | ||
Ok(Arc::new(c)) | ||
}) | ||
} | ||
|
||
pub fn login_with_token(base_path: String, restore_token: String) -> anyhow::Result<Arc<Client>> { | ||
let RestoreToken { session, homeurl, is_guest } = serde_json::from_str(&restore_token)?; | ||
let builder = new_client_builder(base_path, session.user_id.to_string())? | ||
.homeserver_url(&homeurl) | ||
.user_id(&session.user_id); | ||
// First we need to log in. | ||
RUNTIME.block_on(async move { | ||
let client = builder.build().await?; | ||
client.restore_login(session).await?; | ||
let c = Client::new(client, ClientState { is_guest, ..ClientState::default() }); | ||
Ok(Arc::new(c)) | ||
}) | ||
} | ||
|
||
pub fn login_new_client( | ||
base_path: String, | ||
username: String, | ||
password: String, | ||
) -> anyhow::Result<Arc<Client>> { | ||
let builder = new_client_builder(base_path, username.clone())?; | ||
let user = UserId::parse(username)?; | ||
// First we need to log in. | ||
RUNTIME.block_on(async move { | ||
let client = builder.user_id(&user).build().await?; | ||
client.login_username(user.as_str(), &password).send().await?; | ||
let c = Client::new(client, ClientState { is_guest: false, ..ClientState::default() }); | ||
Ok(Arc::new(c)) | ||
}) | ||
} | ||
|
||
fn new_client_builder(base_path: String, home: String) -> anyhow::Result<ClientBuilder> { | ||
let data_path = path::PathBuf::from(base_path).join(sanitize(&home)); | ||
|
||
fs::create_dir_all(&data_path)?; | ||
let store_config = make_store_config(&data_path, None)?; | ||
|
||
Ok(MatrixClient::builder().user_agent("rust-sdk-ios").store_config(store_config)) | ||
} | ||
|
||
#[derive(Default, Debug)] | ||
pub struct ClientState { | ||
is_guest: bool, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ use async_once_cell::OnceCell; | |
use matrix_sdk_base::{locks::RwLock, store::StoreConfig, BaseClient, StateStore}; | ||
use ruma::{ | ||
api::{client::discovery::discover_homeserver, error::FromHttpResponseError, MatrixVersion}, | ||
OwnedServerName, ServerName, UserId, | ||
OwnedServerName, ServerName, | ||
}; | ||
use thiserror::Error; | ||
#[cfg(not(target_arch = "wasm32"))] | ||
|
@@ -84,25 +84,14 @@ impl ClientBuilder { | |
|
||
/// 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. | ||
/// This method is mutually exclusive with | ||
/// [`server_name()`][Self::server_name], 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. | ||
/// | ||
/// `builder.user_id(id)` is a shortcut for | ||
/// `builder.server_name(id.server_name())`. | ||
/// | ||
/// 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(self, user_id: &UserId) -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we please not do this in this PR? I'd rather deprecate it for now, not remove it entirely. |
||
self.server_name(user_id.server_name()) | ||
} | ||
|
||
/// Set the server name to discover the homeserver from. | ||
/// | ||
/// This method is mutually exclusive with | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The use of the new ClientBuilder API highlights that username now has to be passed twice, which is not ideal. The client builder needs username to determine file store path, and potentially homeserver, if not set explicitely. The client needs username to construct user to log in with.
Potentially the client could retrieve the username directly from the builder, but not sure if api like
client.login(password)
is very good.