use std::sync::Mutex; use freya::query::{ Mutation, MutationCapability, MutationStateData, QueriesStorage, Query, QueryCapability, UseMutation, UseQuery, use_mutation, use_query, }; use oneclient_core::LauncherError; use oneclient_auth::{AccountKind, MicrosoftLoginSession, MinecraftAccount}; use uuid::Uuid; static HANDLED_LOGIN_CODE: Mutex> = Mutex::new(None); pub fn login_code_already_handled(user_code: &str) -> bool { let mut handled = HANDLED_LOGIN_CODE.lock().unwrap(); if handled.as_deref() == Some(user_code) { return true; } *handled = Some(user_code.to_string()); false } pub fn reset_login_code_dedup() { *HANDLED_LOGIN_CODE.lock().unwrap() = None; } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ListAccountsKeys; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DefaultAccountKeys { pub fallback: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AccountKeys { pub id: Uuid, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct ListAccountsQuery; impl QueryCapability for ListAccountsQuery { type Ok = Vec; type Err = LauncherError; type Keys = ListAccountsKeys; async fn run(&self, _keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.list_accounts().await) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct DefaultAccountQuery; impl QueryCapability for DefaultAccountQuery { type Ok = Option; type Err = LauncherError; type Keys = DefaultAccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { let state = crate::launcher::state()?; let account = state.auth.default_account().await?; if account.is_some() || !keys.fallback { return Ok(account); } let accounts = state.auth.list_accounts().await; Ok(accounts.into_iter().next()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AccountQuery; impl QueryCapability for AccountQuery { type Ok = Option; type Err = LauncherError; type Keys = AccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.get_account(keys.id).await) } } pub fn use_accounts() -> UseQuery { use_query(Query::new(ListAccountsKeys, ListAccountsQuery)) } pub fn use_default_account(fallback: bool) -> UseQuery { use_query(Query::new( DefaultAccountKeys { fallback }, DefaultAccountQuery, )) } pub fn use_current_account() -> UseQuery { use_default_account(true) } pub fn use_account(id: Uuid) -> UseQuery { use_query(Query::new(AccountKeys { id }, AccountQuery)) } pub fn try_accounts(query: &UseQuery) -> Option> { super::state::settled_or_loading(query) } pub fn try_default_account(query: &UseQuery) -> Option { super::state::settled_or_loading(query).flatten() } pub fn try_account(query: &UseQuery) -> Option { super::state::settled_or_loading(query).flatten() } async fn invalidate_auth_queries(account_id: Option) { QueriesStorage::::try_invalidate_matching(ListAccountsKeys).await; for fallback in [false, true] { QueriesStorage::::try_invalidate_matching(DefaultAccountKeys { fallback, }) .await; } if let Some(id) = account_id { QueriesStorage::::try_invalidate_matching(AccountKeys { id }).await; } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BeginMicrosoftLoginMutation; impl MutationCapability for BeginMicrosoftLoginMutation { type Ok = MicrosoftLoginSession; type Err = LauncherError; type Keys = (); async fn run(&self, _keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.begin_microsoft_login().await?) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct FinishMicrosoftLoginMutation; impl MutationCapability for FinishMicrosoftLoginMutation { type Ok = MinecraftAccount; type Err = LauncherError; type Keys = MicrosoftLoginSession; async fn run(&self, flow: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.finish_microsoft_login(flow.clone()).await?) } async fn on_settled(&self, _keys: &Self::Keys, result: &Result) { if let Ok(account) = result { invalidate_auth_queries(Some(account.id)).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct CancelMicrosoftLoginMutation; /// Keyed by the session's CSRF state token #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct CancelMicrosoftLoginKeys { pub state_token: String, } impl MutationCapability for CancelMicrosoftLoginMutation { type Ok = (); type Err = LauncherError; type Keys = CancelMicrosoftLoginKeys; async fn run(&self, keys: &Self::Keys) -> Result { crate::launcher::state()?.auth.cancel_microsoft_login(&keys.state_token).await; Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AddMicrosoftAccountMutation; impl MutationCapability for AddMicrosoftAccountMutation { type Ok = MinecraftAccount; type Err = LauncherError; type Keys = (); async fn run(&self, _keys: &Self::Keys) -> Result { let state = crate::launcher::state()?; let flow = state.auth.begin_microsoft_login().await?; Ok(state.auth.finish_microsoft_login(flow).await?) } async fn on_settled(&self, _keys: &Self::Keys, result: &Result) { if let Ok(account) = result { invalidate_auth_queries(Some(account.id)).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct AddOfflineAccountMutation; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AddOfflineAccountKeys { pub username: String, } impl MutationCapability for AddOfflineAccountMutation { type Ok = MinecraftAccount; type Err = LauncherError; type Keys = AddOfflineAccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.add_offline_account(keys.username.clone()).await?) } async fn on_settled(&self, _keys: &Self::Keys, result: &Result) { if let Ok(account) = result { invalidate_auth_queries(Some(account.id)).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RemoveAccountMutation; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RemoveAccountKeys { pub id: Uuid, } impl MutationCapability for RemoveAccountMutation { type Ok = (); type Err = LauncherError; type Keys = RemoveAccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.remove_account(keys.id).await?) } async fn on_settled(&self, keys: &Self::Keys, result: &Result) { if result.is_ok() { invalidate_auth_queries(Some(keys.id)).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SetDefaultAccountMutation; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SetDefaultAccountKeys { pub id: Option, } impl MutationCapability for SetDefaultAccountMutation { type Ok = (); type Err = LauncherError; type Keys = SetDefaultAccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.set_default_account(keys.id).await?) } async fn on_settled(&self, keys: &Self::Keys, result: &Result) { if result.is_ok() { invalidate_auth_queries(keys.id).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RefreshAccountMutation; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RefreshAccountKeys { pub id: Uuid, } impl MutationCapability for RefreshAccountMutation { type Ok = MinecraftAccount; type Err = LauncherError; type Keys = RefreshAccountKeys; async fn run(&self, keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.refresh_account(keys.id).await?) } async fn on_settled(&self, keys: &Self::Keys, result: &Result) { if result.is_ok() { invalidate_auth_queries(Some(keys.id)).await; } } } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct RefreshAllAccountsMutation; impl MutationCapability for RefreshAllAccountsMutation { type Ok = Vec; type Err = LauncherError; type Keys = (); async fn run(&self, _keys: &Self::Keys) -> Result { Ok(crate::launcher::state()?.auth.refresh_all_accounts().await?) } async fn on_settled(&self, _keys: &Self::Keys, result: &Result) { if result.is_ok() { invalidate_auth_queries(None).await; } } } pub type UseSetDefaultAccount = UseMutation; pub type UseRemoveAccount = UseMutation; pub type UseRefreshAccount = UseMutation; pub fn use_begin_microsoft_login() -> UseMutation { use_mutation(Mutation::new(BeginMicrosoftLoginMutation)) } pub fn use_finish_microsoft_login() -> UseMutation { use_mutation(Mutation::new(FinishMicrosoftLoginMutation)) } pub fn use_cancel_microsoft_login() -> UseMutation { use_mutation(Mutation::new(CancelMicrosoftLoginMutation)) } pub fn use_add_microsoft_account() -> UseMutation { use_mutation(Mutation::new(AddMicrosoftAccountMutation)) } pub fn use_add_offline_account() -> UseMutation { use_mutation(Mutation::new(AddOfflineAccountMutation)) } pub fn use_remove_account() -> UseMutation { use_mutation(Mutation::new(RemoveAccountMutation)) } pub fn use_set_default_account() -> UseMutation { use_mutation(Mutation::new(SetDefaultAccountMutation)) } pub fn use_refresh_account() -> UseMutation { use_mutation(Mutation::new(RefreshAccountMutation)) } pub fn use_refresh_all_accounts() -> UseMutation { use_mutation(Mutation::new(RefreshAllAccountsMutation)) } /// True before it has ever been run as well as while running For button /// disabled state use [`mutation_is_running`] instead pub fn mutation_is_pending(mutation: &UseMutation) -> bool { matches!( &*mutation.read().state(), MutationStateData::Pending | MutationStateData::Loading { .. } ) } pub fn mutation_is_running(mutation: &UseMutation) -> bool { mutation.read().state().is_loading() } pub fn mutation_error(mutation: &UseMutation) -> Option where M: MutationCapability, M::Err: Clone, { let reader = mutation.read(); match &*reader.state() { MutationStateData::Settled { res: Err(err), .. } => Some(err.clone()), MutationStateData::Loading { res: Some(Err(err)), } => Some(err.clone()), _ => None, } } pub fn accounts_have_microsoft(accounts: &[MinecraftAccount]) -> bool { accounts.iter().any(|a| a.kind == AccountKind::Microsoft) }