diff --git a/src-tauri/src/app_config.rs b/src-tauri/src/app_config.rs index cdb3f15c4..34c614ef0 100644 --- a/src-tauri/src/app_config.rs +++ b/src-tauri/src/app_config.rs @@ -232,7 +232,6 @@ pub(crate) struct AppConfig { created_at: Option>, mode: MiningMode, display_mode: DisplayMode, - auto_mining: bool, mine_on_app_start: bool, p2pool_enabled: bool, last_binaries_update_timestamp: SystemTime, @@ -277,7 +276,6 @@ impl AppConfig { created_at: None, mode: MiningMode::Eco, display_mode: DisplayMode::Light, - auto_mining: true, mine_on_app_start: true, p2pool_enabled: true, last_binaries_update_timestamp: default_system_time(), @@ -589,10 +587,6 @@ impl AppConfig { Ok(()) } - pub fn auto_mining(&self) -> bool { - self.auto_mining - } - pub fn should_auto_launch(&self) -> bool { self.should_auto_launch } diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index eff08598c..95db92d6a 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -39,7 +39,7 @@ use crate::tor_adapter::TorConfig; use crate::utils::shutdown_utils::stop_all_processes; use crate::wallet_adapter::{TransactionInfo, WalletBalance}; use crate::wallet_manager::WalletManagerError; -use crate::{node_adapter, setup_inner, UniverseAppState, APPLICATION_FOLDER_ID}; +use crate::{node_adapter, UniverseAppState, APPLICATION_FOLDER_ID}; use base64::prelude::*; use keyring::Entry; @@ -55,8 +55,6 @@ use std::thread::{available_parallelism, sleep}; use std::time::{Duration, Instant, SystemTime}; use tari_common::configuration::Network; use tauri::{Manager, PhysicalPosition, PhysicalSize}; -use tauri_plugin_sentry::sentry; -use tauri_plugin_sentry::sentry::protocol::Event; const MAX_ACCEPTABLE_COMMAND_TIME: Duration = Duration::from_secs(1); const LOG_TARGET: &str = "tari::universe::commands"; @@ -950,27 +948,6 @@ pub async fn send_feedback( Ok(reference) } -#[tauri::command] -pub async fn set_airdrop_access_token( - token: String, - _window: tauri::Window, - state: tauri::State<'_, UniverseAppState>, - _app: tauri::AppHandle, -) -> Result<(), String> { - let timer = Instant::now(); - let mut write_lock = state.airdrop_access_token.write().await; - *write_lock = Some(token.clone()); - if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME { - warn!(target: LOG_TARGET, - "set_airdrop_access_token took too long: {:?}", - timer.elapsed() - ); - } - let mut in_memory_app_config = state.in_memory_config.write().await; - in_memory_app_config.airdrop_access_token = Some(token); - Ok(()) -} - #[tauri::command] pub async fn set_allow_telemetry( allow_telemetry: bool, @@ -1082,10 +1059,10 @@ pub async fn sign_ws_data(data: String) -> Result { let signature = key.sign(data.as_bytes()); - return Ok(SignWsDataResponse { + Ok(SignWsDataResponse { signature: BASE64_STANDARD.encode(signature.as_ref()), pub_key, - }); + }) } #[tauri::command] @@ -1417,37 +1394,6 @@ pub async fn set_visual_mode<'r>( Ok(()) } -#[tauri::command] -pub async fn setup_application( - state: tauri::State<'_, UniverseAppState>, - app: tauri::AppHandle, -) -> Result { - let timer = Instant::now(); - let rollback = state.setup_counter.write().await; - if rollback.get_value() { - warn!(target: LOG_TARGET, "setup_application has already been initialized, debouncing"); - let res = state.config.read().await.auto_mining(); - return Ok(res); - } - rollback.set_value(true, Duration::from_millis(1000)).await; - setup_inner(state.clone(), app).await.map_err(|e| { - warn!(target: LOG_TARGET, "Error setting up application: {:?}", e); - sentry::capture_event(Event { - level: sentry::Level::Error, - message: Some(e.to_string()), - culprit: Some("setup-inner".to_string()), - ..Default::default() - }); - e.to_string() - })?; - - let res = state.config.read().await.auto_mining(); - if timer.elapsed() > MAX_ACCEPTABLE_COMMAND_TIME { - warn!(target: LOG_TARGET, "setup_application took too long: {:?}", timer.elapsed()); - } - Ok(res) -} - #[allow(clippy::too_many_lines)] #[tauri::command] pub async fn start_mining<'r>( diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9dfc3c5dd..ce1d48882 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -78,7 +78,6 @@ use crate::node_manager::NodeManager; use crate::p2pool::models::P2poolStats; use crate::p2pool_manager::{P2poolConfig, P2poolManager}; use crate::tor_manager::TorManager; -use crate::utils::auto_rollback::AutoRollback; use crate::wallet_manager::WalletManager; #[cfg(target_os = "macos")] use utils::macos_utils::is_app_in_applications_folder; @@ -160,7 +159,7 @@ async fn setup_inner( app: tauri::AppHandle, ) -> Result<(), anyhow::Error> { app.emit( - "message", + "setup_message", SetupStatusEvent { event_type: "setup_status".to_string(), title: "starting-up".to_string(), @@ -168,7 +167,7 @@ async fn setup_inner( progress: 0.0, }, ) - .inspect_err(|e| error!(target: LOG_TARGET, "Could not emit event 'message': {:?}", e))?; + .inspect_err(|e| error!(target: LOG_TARGET, "Could not emit event 'setup_message': {:?}", e))?; #[cfg(target_os = "macos")] if !cfg!(dev) && !is_app_in_applications_folder() { @@ -224,6 +223,7 @@ async fn setup_inner( let cpu_miner_config = state.cpu_miner_config.read().await; let app_config = state.config.read().await; let use_tor = app_config.use_tor(); + let p2pool_enabled = app_config.p2pool_enabled(); drop(app_config); let mm_proxy_manager = state.mm_proxy_manager.clone(); @@ -574,8 +574,17 @@ async fn setup_inner( .await; progress.set_max(75).await; state.node_manager.wait_synced(progress.clone()).await?; + let mut telemetry_id = state + .telemetry_manager + .read() + .await + .get_unique_string() + .await; + if telemetry_id.is_empty() { + telemetry_id = "unknown_miner_tari_universe".to_string(); + } - if state.config.read().await.p2pool_enabled() { + if p2pool_enabled { let _unused = telemetry_service .send( "starting-p2pool".to_string(), @@ -636,7 +645,7 @@ async fn setup_inner( log_path: log_dir.clone(), tari_address: cpu_miner_config.tari_address.clone(), coinbase_extra: telemetry_id, - p2pool_enabled: config.p2pool_enabled(), + p2pool_enabled, monero_nodes: config.mmproxy_monero_nodes().clone(), use_monero_fail: config.mmproxy_use_monero_fail(), }) @@ -655,7 +664,7 @@ async fn setup_inner( drop( app.clone() .emit( - "message", + "setup_message", SetupStatusEvent { event_type: "setup_status".to_string(), title: "application-started".to_string(), @@ -663,7 +672,9 @@ async fn setup_inner( progress: 1.0, }, ) - .inspect_err(|e| error!(target: LOG_TARGET, "Could not emit event 'message': {:?}", e)), + .inspect_err( + |e| error!(target: LOG_TARGET, "Could not emit event 'setup_message': {:?}", e), + ), ); let move_handle = app.clone(); @@ -789,7 +800,6 @@ struct UniverseAppState { updates_manager: UpdatesManager, cached_p2pool_connections: Arc>>>, cached_miner_metrics: Arc>>, - setup_counter: Arc>>, } #[derive(Clone, serde::Serialize)] @@ -798,6 +808,11 @@ struct Payload { cwd: String, } +#[derive(Clone, serde::Serialize, serde::Deserialize)] +struct FEPayload { + token: String, +} + #[allow(clippy::too_many_lines)] fn main() { let _unused = fix_path_env::fix(); @@ -895,10 +910,10 @@ fn main() { updates_manager, cached_p2pool_connections: Arc::new(RwLock::new(None)), cached_miner_metrics: Arc::new(RwLock::new(None)), - setup_counter: Arc::new(RwLock::new(AutoRollback::new(false))), }; let app_state2 = app_state.clone(); + let app = tauri::Builder::default() .plugin(tauri_plugin_process::init()) .plugin(tauri_plugin_sentry::init_with_no_injection(&client)) @@ -972,6 +987,24 @@ fn main() { } }; + let token_state_clone = app.state::().airdrop_access_token.clone(); + let memory_state_clone = app.state::().in_memory_config.clone(); + app.listen("airdrop_token", move |event| { + let token_value = token_state_clone.clone(); + let memory_value = memory_state_clone.clone(); + tauri::async_runtime::spawn(async move { + info!(target: LOG_TARGET, "Getting token from Frontend"); + let payload = event.payload(); + let res = serde_json::from_str::(payload).expect("No token"); + + let token = res.token; + let mut lock = token_value.write().await; + *lock = Some(token.clone()); + + let mut in_memory_app_config = memory_value.write().await; + in_memory_app_config.airdrop_access_token = Some(token); + }); + }); // The start of needed restart operations. Break this out into a module if we need n+1 let tcp_tor_toggled_file = config_path.join("tcp_tor_toggled"); if tcp_tor_toggled_file.exists() { @@ -1092,7 +1125,6 @@ fn main() { commands::resolve_application_language, commands::restart_application, commands::send_feedback, - commands::set_airdrop_access_token, commands::set_allow_telemetry, commands::send_data_telemetry_service, commands::set_application_language, @@ -1112,7 +1144,6 @@ fn main() { commands::set_tor_config, commands::set_use_tor, commands::set_visual_mode, - commands::setup_application, commands::start_mining, commands::stop_mining, commands::update_applications, @@ -1139,11 +1170,15 @@ fn main() { ); app.run(move |app_handle, event| match event { - tauri::RunEvent::Ready { .. } => { + tauri::RunEvent::Ready => { info!(target: LOG_TARGET, "App is ready"); + let a = app_handle.clone(); let app_handle_clone = app_handle.clone(); - tauri::async_runtime::spawn(async move { - let _unused = listen_to_frontend_ready(app_handle_clone).await; + tauri::async_runtime::spawn( async move { + let state = app_handle_clone.state::().clone(); + let _unused = listen_to_frontend_ready(app_handle_clone.clone()).await; + let _res = setup_inner(state, a.clone()).await + .inspect_err(|e| error!(target: LOG_TARGET, "Could not setup app: {:?}", e)); }); } tauri::RunEvent::ExitRequested { api: _, .. } => { diff --git a/src-tauri/src/progress_tracker.rs b/src-tauri/src/progress_tracker.rs index 93b388836..6a495685c 100644 --- a/src-tauri/src/progress_tracker.rs +++ b/src-tauri/src/progress_tracker.rs @@ -123,7 +123,7 @@ impl ProgressTrackerInner { } self.app_handle .emit( - "message", + "setup_message", SetupStatusEvent { event_type: "setup_status".to_string(), title, @@ -131,7 +131,9 @@ impl ProgressTrackerInner { progress: progress_percentage, }, ) - .inspect_err(|e| error!(target: LOG_TARGET, "Could not emit event 'message': {:?}", e)) + .inspect_err( + |e| error!(target: LOG_TARGET, "Could not emit event 'setup_message': {:?}", e), + ) .ok(); } } diff --git a/src-tauri/src/telemetry_manager.rs b/src-tauri/src/telemetry_manager.rs index 2e87a0f0e..82182d116 100644 --- a/src-tauri/src/telemetry_manager.rs +++ b/src-tauri/src/telemetry_manager.rs @@ -280,11 +280,11 @@ impl TelemetryManager { pub async fn initialize( &mut self, airdrop_access_token: Arc>>, - app: tauri::AppHandle, + app_handle: tauri::AppHandle, ) -> Result<()> { info!(target: LOG_TARGET, "Starting telemetry manager"); self.airdrop_access_token = airdrop_access_token.clone(); - self.start_telemetry_process(TelemetryFrequency::default().into(), app.clone()) + self.start_telemetry_process(TelemetryFrequency::default().into(), app_handle) .await?; Ok(()) } @@ -292,7 +292,7 @@ impl TelemetryManager { async fn start_telemetry_process( &mut self, timeout: Duration, - app: tauri::AppHandle, + app_handle: tauri::AppHandle, ) -> Result<(), TelemetryManagerError> { let cpu_miner = self.cpu_miner.clone(); let gpu_status = self.gpu_status.clone(); @@ -314,7 +314,7 @@ impl TelemetryManager { let airdrop_access_token_validated = validate_jwt(airdrop_access_token.clone()).await; let telemetry_data = get_telemetry_data(&cpu_miner, &gpu_status, &node_status, &p2pool_status, &config, network).await; let airdrop_api_url = in_memory_config_cloned.read().await.airdrop_api_url.clone(); - handle_telemetry_data(telemetry_data, airdrop_api_url, airdrop_access_token_validated, app.clone()).await; + handle_telemetry_data(telemetry_data, airdrop_api_url, airdrop_access_token_validated, app_handle.clone()).await; } sleep(timeout); } @@ -577,7 +577,7 @@ async fn handle_telemetry_data( telemetry: Result, airdrop_api_url: String, airdrop_access_token: Option, - app: tauri::AppHandle, + app_handle: tauri::AppHandle, ) { match telemetry { Ok(telemetry) => { @@ -610,7 +610,8 @@ async fn handle_telemetry_data( referral_count: response_inner, }; - app.emit("UserPoints", emit_data) + app_handle + .emit("UserPoints", emit_data) .map_err(|e| { error!("could not send user points as an event: {}", e) }) diff --git a/src-tauri/src/tests/app_config_tests.rs b/src-tauri/src/tests/app_config_tests.rs index d93161369..c5fd1e244 100644 --- a/src-tauri/src/tests/app_config_tests.rs +++ b/src-tauri/src/tests/app_config_tests.rs @@ -34,7 +34,6 @@ mod tests { let config_json = r#" { "version": 0, - "auto_mining": true } "#; @@ -43,8 +42,6 @@ mod tests { config.apply_loaded_config(config_json.to_string()); assert_eq!(config.mode(), MiningMode::Eco); - // it doesn't affect auto_mining value saved in the config - assert_eq!(config.auto_mining(), true); assert_eq!(config.p2pool_enabled(), false); assert_ne!(format!("{:?}", config.last_binaries_update_timestamp()), ""); assert_eq!(config.allow_telemetry(), false); @@ -59,7 +56,6 @@ mod tests { let config_json = r#" { "mode": "Ludicrous", - "auto_mining": false, "p2pool_enabled": true, "last_binaries_update_timestamp": { "secs_since_epoch": 1725545367, @@ -78,7 +74,6 @@ mod tests { config.apply_loaded_config(config_json.to_string()); assert_eq!(config.mode(), MiningMode::Ludicrous); - assert_eq!(config.auto_mining(), false); // For now always false by default assert_eq!(config.p2pool_enabled(), false); let expected_timestamp = SystemTime::UNIX_EPOCH + Duration::new(1725545367, 379078628); diff --git a/src-tauri/src/utils/auto_rollback.rs b/src-tauri/src/utils/auto_rollback.rs deleted file mode 100644 index ee5a72524..000000000 --- a/src-tauri/src/utils/auto_rollback.rs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2024. The Tari Project -// -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: -// -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. -// -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. -// -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -use std::sync::{Arc, Mutex}; -use tokio::time::{sleep, Duration}; - -#[derive(Debug)] -pub struct AutoRollback { - value: Arc>, - initial_value: T, -} - -impl AutoRollback -where - T: Clone + Send + 'static, -{ - pub fn new(value: T) -> Self { - AutoRollback { - value: Arc::new(Mutex::new(value.clone())), - initial_value: value, - } - } - - pub async fn set_value(&self, new_value: T, rollback_delay: Duration) { - // Update the value - { - if let Ok(mut val) = self.value.lock() { - *val = new_value; - } - } - - // Spawn a task that will rollback the value after the delay - let value_clone = Arc::clone(&self.value); - let initial_value = self.initial_value.clone(); - tokio::spawn(async move { - sleep(rollback_delay).await; - - println!("Rollback finished after {:?}", rollback_delay); - // Rollback to the initial value - if let Ok(mut val) = value_clone.lock() { - *val = initial_value; - }; - }); - } - - pub fn get_value(&self) -> T - where - T: Clone, - { - if let Ok(val) = self.value.lock() { - val.clone() - } else { - self.initial_value.clone() - } - } -} diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index de2665845..71db7f628 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -20,7 +20,6 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -pub mod auto_rollback; pub mod file_utils; pub mod logging_utils; pub mod macos_utils; diff --git a/src/App/AppWrapper.tsx b/src/App/AppWrapper.tsx index c5e1c2886..bb515db96 100644 --- a/src/App/AppWrapper.tsx +++ b/src/App/AppWrapper.tsx @@ -3,20 +3,17 @@ import { initSystray } from '@app/utils'; import { useDetectMode, useDisableRefresh, useLangaugeResolver, useListenForExternalDependencies } from '@app/hooks'; -import { useAppConfigStore } from '../store/useAppConfigStore.ts'; +import { fetchAppConfig } from '../store/useAppConfigStore.ts'; import setupLogger from '../utils/shared-logger.ts'; -import App from './App.tsx'; import useListenForCriticalProblem from '@app/hooks/useListenForCriticalProblem.tsx'; -import { useMiningStore } from '@app/store/useMiningStore.ts'; +import { setMiningNetwork } from '@app/store/miningStoreActions.ts'; +import App from './App.tsx'; // FOR ANYTHING THAT NEEDS TO BE INITIALISED setupLogger(); export default function AppWrapper() { - const fetchAppConfig = useAppConfigStore((s) => s.fetchAppConfig); - const setMiningNetwork = useMiningStore((s) => s.setMiningNetwork); - useDetectMode(); useDisableRefresh(); useLangaugeResolver(); @@ -29,8 +26,7 @@ export default function AppWrapper() { await initSystray(); await setMiningNetwork(); } - initialize(); - // eslint-disable-next-line react-hooks/exhaustive-deps + void initialize(); }, []); return ; diff --git a/src/containers/floating/ExternalDependenciesDialog/ExternalDependenciesDialog.tsx b/src/containers/floating/ExternalDependenciesDialog/ExternalDependenciesDialog.tsx index 2484f368c..a55ac79a2 100644 --- a/src/containers/floating/ExternalDependenciesDialog/ExternalDependenciesDialog.tsx +++ b/src/containers/floating/ExternalDependenciesDialog/ExternalDependenciesDialog.tsx @@ -16,11 +16,7 @@ export const ExternalDependenciesDialog = () => { const showExternalDependenciesDialog = useUIStore((s) => s.showExternalDependenciesDialog); const setShowExternalDependenciesDialog = useUIStore((s) => s.setShowExternalDependenciesDialog); const externalDependencies = useAppStateStore((s) => s.externalDependencies); - const setView = useUIStore((s) => s.setView); - const setCriticalError = useAppStateStore((s) => s.setCriticalError); const [isRestarting, setIsRestarting] = useState(false); - const [isInitializing, setIsInitializing] = useState(false); - const [installationSlot, setInstallationSlot] = useState(null); const handleRestart = useCallback(async () => { @@ -33,20 +29,6 @@ export const ExternalDependenciesDialog = () => { setIsRestarting(false); }, []); - const handleContinue = useCallback(() => { - setShowExternalDependenciesDialog(false); - if (isInitializing) return; - setIsInitializing(true); - invoke('setup_application') - .catch((e) => { - setCriticalError(`Failed to setup application: ${e}`); - setView('mining'); - }) - .then(() => { - setIsInitializing(false); - }); - }, [setCriticalError, setShowExternalDependenciesDialog, setView, isInitializing]); - const shouldAllowContinue = Object.values(externalDependencies).every( (missingDependency) => missingDependency.status === ExternalDependencyStatus.Installed ); @@ -77,7 +59,7 @@ export const ExternalDependenciesDialog = () => { setShowExternalDependenciesDialog(false)} disabled={isRestarting || !shouldAllowContinue} style={{ width: '100px' }} > diff --git a/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx b/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx index fcfbc5c9e..2f899bb68 100644 --- a/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx +++ b/src/containers/floating/Settings/sections/airdrop/ApplyInviteCode.tsx @@ -12,7 +12,7 @@ import { useCallback, useEffect, useState } from 'react'; import { Input } from '@app/components/elements/inputs/Input'; import { v4 as uuidv4 } from 'uuid'; -import { useAirdropStore } from '@app/store/useAirdropStore'; +import { setAirdropTokens, useAirdropStore } from '@app/store/useAirdropStore'; import { useAppConfigStore } from '@app/store/useAppConfigStore'; import { open } from '@tauri-apps/plugin-shell'; @@ -25,7 +25,7 @@ export const ApplyInviteCode = () => { const [claimCode, setClaimCode] = useState(''); const [loading, setLoading] = useState(false); - const { authUuid, setAuthUuid, setAirdropTokens, backendInMemoryConfig } = useAirdropStore(); + const { authUuid, setAuthUuid, backendInMemoryConfig } = useAirdropStore(); const handleAuth = useCallback(() => { const token = uuidv4(); @@ -35,7 +35,7 @@ export const ApplyInviteCode = () => { setAllowTelemetry(true).then(() => { setAuthUuid(token); - open(refUrl); + void open(refUrl); }); } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -52,8 +52,13 @@ export const ApplyInviteCode = () => { .then((response) => response.json()) .then((data) => { if (!data.error) { - setAirdropTokens(data); - return true; + setAirdropTokens(data) + .then(() => { + return true; + }) + .catch(() => { + return false; + }); } }) .catch((e) => { @@ -63,7 +68,6 @@ export const ApplyInviteCode = () => { return false; } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [authUuid, backendInMemoryConfig?.airdropApiUrl]); useEffect(() => { @@ -88,7 +92,7 @@ export const ApplyInviteCode = () => { }; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [authUuid, backendInMemoryConfig?.airdropApiUrl, handleToken]); + }, [authUuid, backendInMemoryConfig?.airdropApiUrl, {}]); return ( diff --git a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx index 8eb469bc4..662b12dd8 100644 --- a/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx +++ b/src/containers/main/Airdrop/AirdropGiftTracker/sections/LoggedOut/LoggedOut.tsx @@ -1,4 +1,4 @@ -import { GIFT_GEMS, useAirdropStore } from '@app/store/useAirdropStore'; +import { GIFT_GEMS, setAirdropTokens, useAirdropStore } from '@app/store/useAirdropStore'; import { ClaimButton, GemPill, Image, Title, Wrapper } from './styles'; import { useCallback, useEffect, useState } from 'react'; import { open } from '@tauri-apps/plugin-shell'; @@ -12,7 +12,7 @@ export default function LoggedOut() { const [modalIsOpen, setModalIsOpen] = useState(false); const { t } = useTranslation(['airdrop'], { useSuspense: false }); const restartMining = useMiningStore((s) => s.restartMining); - const { referralQuestPoints, authUuid, setAuthUuid, setAirdropTokens, backendInMemoryConfig } = useAirdropStore(); + const { referralQuestPoints, authUuid, setAuthUuid, backendInMemoryConfig } = useAirdropStore(); const handleAuth = useCallback( (code?: string) => { diff --git a/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts b/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts index f7c657e18..bfc1e7edf 100644 --- a/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts +++ b/src/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts @@ -1,5 +1,5 @@ -import { AirdropTokens, useAirdropStore } from '@app/store/useAirdropStore'; -import { useCallback, useEffect } from 'react'; +import { AirdropTokens, setAirdropTokens, useAirdropStore } from '@app/store/useAirdropStore'; +import { useEffect } from 'react'; export async function fetchAirdropTokens(airdropApiUrl: string, airdropTokens: AirdropTokens) { const response = await fetch(`${airdropApiUrl}/auth/local/refresh`, { @@ -19,40 +19,32 @@ export async function fetchAirdropTokens(airdropApiUrl: string, airdropTokens: A return data; } -export function useHandleAirdropTokensRefresh() { - const { airdropTokens, setAirdropTokens } = useAirdropStore(); - const syncedAidropWithBackend = useAirdropStore((s) => s.syncedWithBackend); +export async function handleRefreshAirdropTokens(airdropApiUrl: string) { + const airdropTokens = useAirdropStore.getState().airdropTokens; + let tokens: AirdropTokens | undefined = airdropTokens; + // 5 hours from now + const expirationLimit = new Date(new Date().getTime() + 1000 * 60 * 60 * 5); + const tokenExpirationTime = airdropTokens?.expiresAt && new Date(airdropTokens?.expiresAt * 1000); - return useCallback( - async (airdropApiUrl: string) => { - let fetchedAirdropTokens: AirdropTokens | undefined; - // 5 hours from now - const expirationLimit = new Date(new Date().getTime() + 1000 * 60 * 60 * 5); - const tokenExpirationTime = airdropTokens?.expiresAt && new Date(airdropTokens?.expiresAt * 1000); + const tokenHasExpired = tokenExpirationTime && tokenExpirationTime < expirationLimit; + if (airdropTokens && tokenHasExpired) { + try { + tokens = await fetchAirdropTokens(airdropApiUrl, airdropTokens); + } catch (error) { + console.error('Error refreshing airdrop tokens:', error); + } + } - const tokenHasExpired = tokenExpirationTime && tokenExpirationTime < expirationLimit; - if (airdropTokens && (!syncedAidropWithBackend || tokenHasExpired)) { - try { - fetchedAirdropTokens = await fetchAirdropTokens(airdropApiUrl, airdropTokens); - } catch (error) { - console.error('Error refreshing airdrop tokens:', error); - } - } - await setAirdropTokens(fetchedAirdropTokens); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [airdropTokens, syncedAidropWithBackend] - ); + await setAirdropTokens(tokens); } export function useAirdropTokensRefresh() { - const { backendInMemoryConfig } = useAirdropStore(); - - // Handle refreshing the access token - const handleRefresh = useHandleAirdropTokensRefresh(); - + const backendInMemoryConfig = useAirdropStore((s) => s.backendInMemoryConfig); useEffect(() => { if (!backendInMemoryConfig?.airdropApiUrl) return; - const interval = setInterval(() => handleRefresh(backendInMemoryConfig?.airdropApiUrl), 1000 * 60 * 60); + const interval = setInterval( + () => handleRefreshAirdropTokens(backendInMemoryConfig?.airdropApiUrl), + 1000 * 60 * 60 + ); return () => clearInterval(interval); - }, [handleRefresh, backendInMemoryConfig?.airdropApiUrl]); + }, [backendInMemoryConfig?.airdropApiUrl]); } diff --git a/src/hooks/app/useSetUp.ts b/src/hooks/app/useSetUp.ts index badbf25ab..d9a71c1b1 100644 --- a/src/hooks/app/useSetUp.ts +++ b/src/hooks/app/useSetUp.ts @@ -3,36 +3,16 @@ import { useCallback, useEffect, useRef } from 'react'; import { listen } from '@tauri-apps/api/event'; import { TauriEvent } from '../../types.ts'; -import { invoke } from '@tauri-apps/api/core'; - import { useAppStateStore } from '../../store/appStateStore.ts'; - -import { useAirdropStore } from '@app/store/useAirdropStore.ts'; -import { useHandleAirdropTokensRefresh } from '../airdrop/stateHelpers/useAirdropTokensRefresh.ts'; +import { fetchBackendInMemoryConfig } from '@app/store/useAirdropStore.ts'; +import { handleRefreshAirdropTokens } from '@app/hooks/airdrop/stateHelpers/useAirdropTokensRefresh.ts'; export function useSetUp() { const isInitializingRef = useRef(false); - const handleRefreshAirdropTokens = useHandleAirdropTokensRefresh(); const adminShow = useUIStore((s) => s.adminShow); const setSetupDetails = useAppStateStore((s) => s.setSetupDetails); - const setCriticalError = useAppStateStore((s) => s.setCriticalError); const setSettingUpFinished = useAppStateStore((s) => s.setSettingUpFinished); - const fetchApplicationsVersionsWithRetry = useAppStateStore((s) => s.fetchApplicationsVersionsWithRetry); - const syncedAidropWithBackend = useAirdropStore((s) => s.syncedWithBackend); - - const fetchBackendInMemoryConfig = useAirdropStore((s) => s.fetchBackendInMemoryConfig); - - useEffect(() => { - const refreshTokens = async () => { - const backendInMemoryConfig = await fetchBackendInMemoryConfig(); - if (backendInMemoryConfig?.airdropApiUrl) { - await handleRefreshAirdropTokens(backendInMemoryConfig.airdropApiUrl); - } - }; - refreshTokens(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); const clearStorage = useCallback(() => { // clear all storage except airdrop data @@ -46,12 +26,21 @@ export function useSetUp() { const handlePostSetup = useCallback(async () => { await fetchApplicationsVersionsWithRetry(); await setSettingUpFinished(); - // eslint-disable-next-line react-hooks/exhaustive-deps + }, [fetchApplicationsVersionsWithRetry, setSettingUpFinished]); + + useEffect(() => { + async function initWithToken() { + const beConfig = await fetchBackendInMemoryConfig(); + if (beConfig?.airdropUrl) { + await handleRefreshAirdropTokens(beConfig.airdropUrl); + } + } + void initWithToken(); }, []); useEffect(() => { if (adminShow === 'setup') return; - const unlistenPromise = listen('message', async ({ event: e, payload: p }: TauriEvent) => { + const unlistenPromise = listen('setup_message', async ({ event: e, payload: p }: TauriEvent) => { switch (p.event_type) { case 'setup_status': if (p.progress > 0) { @@ -66,17 +55,13 @@ export function useSetUp() { break; } }); - if (syncedAidropWithBackend && !isInitializingRef.current) { + + if (!isInitializingRef.current) { isInitializingRef.current = true; clearStorage(); - invoke('setup_application').catch((e) => { - console.error(`Failed to setup application: ${e}`); - setCriticalError(`Failed to setup application: ${e}`); - }); } return () => { unlistenPromise.then((unlisten) => unlisten()); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [clearStorage, handlePostSetup, adminShow, syncedAidropWithBackend]); + }, [clearStorage, handlePostSetup, adminShow, setSetupDetails]); } diff --git a/src/store/miningStoreActions.ts b/src/store/miningStoreActions.ts index 56a9c1949..8cee9027f 100644 --- a/src/store/miningStoreActions.ts +++ b/src/store/miningStoreActions.ts @@ -84,4 +84,16 @@ const changeMiningMode = async (params: ChangeMiningModeArgs) => { } }; -export { startMining, pauseMining, stopMining, changeMiningMode }; +const setMiningNetwork = async () => { + try { + const network = (await invoke('get_network', {})) as string; + useMiningStore.setState({ network }); + } catch (e) { + const appStateStore = useAppStateStore.getState(); + console.error('Could not get network: ', e); + appStateStore.setError(e as string); + useMiningStore.setState({ excludedGpuDevices: undefined }); + } +}; + +export { startMining, pauseMining, stopMining, changeMiningMode, setMiningNetwork }; diff --git a/src/store/useAirdropStore.ts b/src/store/useAirdropStore.ts index 84bab011b..56ec7bdb5 100644 --- a/src/store/useAirdropStore.ts +++ b/src/store/useAirdropStore.ts @@ -2,6 +2,7 @@ import { createWithEqualityFn as create } from 'zustand/traditional'; import { persist } from 'zustand/middleware'; import { invoke } from '@tauri-apps/api/core'; import { useMiningStore } from './useMiningStore'; +import { getCurrentWindow } from '@tauri-apps/api/window'; export const GIFT_GEMS = 5000; export const REFERRAL_GEMS = 5000; @@ -143,10 +144,9 @@ interface AirdropStore extends AirdropState { setReferralQuestPoints: (referralQuestPoints: ReferralQuestPoints) => void; setMiningRewardPoints: (miningRewardPoints?: MiningPoint) => void; setAuthUuid: (authUuid: string) => void; - setAirdropTokens: (airdropToken?: AirdropTokens) => Promise; + setUserDetails: (userDetails?: UserDetails) => void; setUserPoints: (userPoints: UserPoints) => void; - fetchBackendInMemoryConfig: (config?: BackendInMemoryConfig) => Promise; setReferralCount: (referralCount: ReferralCount) => void; setFlareAnimationType: (flareAnimationType?: AnimationType) => void; setBonusTiers: (bonusTiers: BonusTier[]) => void; @@ -177,25 +177,6 @@ export const useAirdropStore = create()( setBonusTiers: (bonusTiers) => set({ bonusTiers }), setUserDetails: (userDetails) => set({ userDetails }), setAuthUuid: (authUuid) => set({ authUuid }), - setAirdropTokens: async (airdropTokens) => { - if (airdropTokens) { - try { - await invoke('set_airdrop_access_token', { token: airdropTokens.token }); - } catch (error) { - console.error('Error getting airdrop tokens:', error); - } - set({ - syncedWithBackend: true, - airdropTokens: { - ...airdropTokens, - expiresAt: parseJwt(airdropTokens.token).exp, - }, - }); - } else { - // User not connected - set({ syncedWithBackend: true }); - } - }, setReferralCount: (referralCount) => set({ referralCount }), setUserPoints: (userPoints) => set({ userPoints, referralCount: userPoints?.referralCount }), setUserGems: (userGems: number) => @@ -209,18 +190,7 @@ export const useAirdropStore = create()( userPoints: userPointsFormatted, }; }), - fetchBackendInMemoryConfig: async () => { - let backendInMemoryConfig: BackendInMemoryConfig | undefined = undefined; - try { - backendInMemoryConfig = await invoke('get_app_in_memory_config', {}); - set({ backendInMemoryConfig }); - } catch (e) { - console.error('get_app_in_memory_config error:', e); - } - return backendInMemoryConfig; - }, setMiningRewardPoints: (miningRewardPoints) => set({ miningRewardPoints, flareAnimationType: 'BonusGems' }), - logout: async () => { set(clearState); await useMiningStore.getState().restartMining(); @@ -236,3 +206,31 @@ export const useAirdropStore = create()( } ) ); + +export const setAirdropTokens = async (airdropTokens?: AirdropTokens) => { + const currentWindow = getCurrentWindow(); + if (airdropTokens) { + await currentWindow?.emit('airdrop_token', { token: airdropTokens.token }); + useAirdropStore.setState({ + syncedWithBackend: true, + airdropTokens: { + ...airdropTokens, + expiresAt: parseJwt(airdropTokens.token).exp, + }, + }); + } else { + // User not connected + useAirdropStore.setState({ syncedWithBackend: true }); + } +}; + +export const fetchBackendInMemoryConfig = async () => { + let backendInMemoryConfig: BackendInMemoryConfig | undefined = undefined; + try { + backendInMemoryConfig = await invoke('get_app_in_memory_config', {}); + useAirdropStore.setState({ backendInMemoryConfig }); + } catch (e) { + console.error('get_app_in_memory_config error:', e); + } + return backendInMemoryConfig; +}; diff --git a/src/store/useAppConfigStore.ts b/src/store/useAppConfigStore.ts index 18d5c8e28..73b9945b4 100644 --- a/src/store/useAppConfigStore.ts +++ b/src/store/useAppConfigStore.ts @@ -18,7 +18,6 @@ interface SetModeProps { } interface Actions { - fetchAppConfig: () => Promise; setAllowTelemetry: (allowTelemetry: boolean) => Promise; setCpuMiningEnabled: (enabled: boolean) => Promise; setGpuMiningEnabled: (enabled: boolean) => Promise; @@ -69,25 +68,9 @@ const initialState: State = { pre_release: false, }; -export const useAppConfigStore = create()((set, getState) => ({ +export const useAppConfigStore = create()((set) => ({ ...initialState, - fetchAppConfig: async () => { - try { - const appConfig = await invoke('get_app_config'); - set(appConfig); - const configTheme = appConfig.display_mode?.toLowerCase(); - const canvasElement = document.getElementById('canvas'); - if (canvasElement && !appConfig.visual_mode) { - canvasElement.style.display = 'none'; - } - if (configTheme) { - await getState().setTheme(configTheme as displayMode); - } - } catch (e) { - console.error('Could not get app config: ', e); - } - }, setShouldAutoLaunch: async (shouldAutoLaunch) => { set({ should_auto_launch: shouldAutoLaunch }); invoke('set_should_auto_launch', { shouldAutoLaunch }).catch((e) => { @@ -317,3 +300,23 @@ export const useAppConfigStore = create()((set, getState) = }); }, })); + +export const fetchAppConfig = async () => { + try { + const appConfig = await invoke('get_app_config'); + useAppConfigStore.setState(appConfig); + const configTheme = appConfig.display_mode?.toLowerCase(); + const canvasElement = document.getElementById('canvas'); + if (canvasElement && !appConfig.visual_mode) { + canvasElement.style.display = 'none'; + } + + if (configTheme) { + await useAppConfigStore.getState().setTheme(configTheme as displayMode); + } + + return appConfig; + } catch (e) { + console.error('Could not get app config: ', e); + } +}; diff --git a/src/store/useMiningStore.ts b/src/store/useMiningStore.ts index 8ca81d906..4185392e3 100644 --- a/src/store/useMiningStore.ts +++ b/src/store/useMiningStore.ts @@ -1,6 +1,5 @@ import { MaxConsumptionLevels } from '@app/types/app-status'; import { create } from './create'; -import * as Sentry from '@sentry/react'; import { invoke } from '@tauri-apps/api/core'; import { useAppStateStore } from './appStateStore'; @@ -21,9 +20,7 @@ interface State { interface Actions { restartMining: () => Promise; - setMiningNetwork: () => Promise; setMiningControlsEnabled: (miningControlsEnabled: boolean) => void; - setIsChangingMode: (isChangingMode: boolean) => void; setExcludedGpuDevice: (excludeGpuDevice: number[]) => Promise; setCustomLevelsDialogOpen: (customLevelsDialogOpen: boolean) => void; getMaxAvailableThreads: () => void; @@ -46,18 +43,6 @@ const initialState: State = { export const useMiningStore = create()((set) => ({ ...initialState, setCustomLevelsDialogOpen: (customLevelsDialogOpen) => set({ customLevelsDialogOpen }), - setMiningNetwork: async () => { - try { - const network = (await invoke('get_network', {})) as string; - set({ network }); - } catch (e) { - Sentry.captureException(e); - const appStateStore = useAppStateStore.getState(); - console.error('Could not get network: ', e); - appStateStore.setError(e as string); - set({ excludedGpuDevices: undefined }); - } - }, getMaxAvailableThreads: async () => { console.info('Getting max available threads...'); try { @@ -88,7 +73,6 @@ export const useMiningStore = create()((set) => ({ } }, setMiningControlsEnabled: (miningControlsEnabled) => set({ miningControlsEnabled }), - setIsChangingMode: (isChangingMode) => set({ isChangingMode }), setExcludedGpuDevice: async (excludedGpuDevices) => { set({ excludedGpuDevices }); try { diff --git a/src/types/invoke.ts b/src/types/invoke.ts index 0ddaffcb6..955baeed1 100644 --- a/src/types/invoke.ts +++ b/src/types/invoke.ts @@ -30,7 +30,6 @@ declare module '@tauri-apps/api/core' { function invoke(param: 'get_paper_wallet_details'): Promise; function invoke(param: 'resolve_application_language'): Promise; function invoke(param: 'set_mine_on_app_start', payload: { mineOnAppStart: boolean }): Promise; - function invoke(param: 'setup_application'): Promise; function invoke(param: 'open_log_dir'): Promise; function invoke(param: 'start_mining'): Promise; function invoke(param: 'stop_mining'): Promise;