mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-05-02 06:46:06 -04:00
feat(tauri): Initialize Context in background (#59)
This PR does the following: - The Context (including Bitcoin wallet, Monero wallet, ...) is initialized in the background. This allows the window to be displayed instantly upon startup. - Host sends events to Guest about progress of Context initialization. Those events are used to display an alert in the navigation bar. - If a Tauri command is invoked which requires the Context to be available, an error will be returned - As soon as the Context becomes available the `Guest` requests the history and Bitcoin balance - Re-enables Material UI animations
This commit is contained in:
parent
792fbbf746
commit
e4141c763b
17 changed files with 369 additions and 191 deletions
|
@ -6,18 +6,19 @@ use swap::cli::{
|
|||
BalanceArgs, BuyXmrArgs, GetHistoryArgs, GetSwapInfosAllArgs, ResumeSwapArgs,
|
||||
SuspendCurrentSwapArgs, WithdrawBtcArgs,
|
||||
},
|
||||
tauri_bindings::TauriHandle,
|
||||
tauri_bindings::{TauriContextStatusEvent, TauriEmitter, TauriHandle},
|
||||
Context, ContextBuilder,
|
||||
},
|
||||
command::{Bitcoin, Monero},
|
||||
};
|
||||
use tauri::{Manager, RunEvent};
|
||||
use tauri::{async_runtime::RwLock, Manager, RunEvent};
|
||||
|
||||
/// Trait to convert Result<T, E> to Result<T, String>
|
||||
/// Tauri commands require the error type to be a string
|
||||
trait ToStringResult<T> {
|
||||
fn to_string_result(self) -> Result<T, String>;
|
||||
}
|
||||
|
||||
// Implement the trait for Result<T, E>
|
||||
impl<T, E: ToString> ToStringResult<T> for Result<T, E> {
|
||||
fn to_string_result(self) -> Result<T, String> {
|
||||
self.map_err(|e| e.to_string())
|
||||
|
@ -53,42 +54,72 @@ macro_rules! tauri_command {
|
|||
($fn_name:ident, $request_name:ident) => {
|
||||
#[tauri::command]
|
||||
async fn $fn_name(
|
||||
context: tauri::State<'_, Arc<Context>>,
|
||||
context: tauri::State<'_, RwLock<State>>,
|
||||
args: $request_name,
|
||||
) -> Result<<$request_name as swap::cli::api::request::Request>::Response, String> {
|
||||
<$request_name as swap::cli::api::request::Request>::request(
|
||||
args,
|
||||
context.inner().clone(),
|
||||
)
|
||||
.await
|
||||
.to_string_result()
|
||||
// Throw error if context is not available
|
||||
let context = context.read().await.try_get_context()?;
|
||||
|
||||
<$request_name as swap::cli::api::request::Request>::request(args, context)
|
||||
.await
|
||||
.to_string_result()
|
||||
}
|
||||
};
|
||||
($fn_name:ident, $request_name:ident, no_args) => {
|
||||
#[tauri::command]
|
||||
async fn $fn_name(
|
||||
context: tauri::State<'_, Arc<Context>>,
|
||||
context: tauri::State<'_, RwLock<State>>,
|
||||
) -> Result<<$request_name as swap::cli::api::request::Request>::Response, String> {
|
||||
<$request_name as swap::cli::api::request::Request>::request(
|
||||
$request_name {},
|
||||
context.inner().clone(),
|
||||
)
|
||||
.await
|
||||
.to_string_result()
|
||||
// Throw error if context is not available
|
||||
let context = context.read().await.try_get_context()?;
|
||||
|
||||
<$request_name as swap::cli::api::request::Request>::request($request_name {}, context)
|
||||
.await
|
||||
.to_string_result()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
tauri_command!(get_balance, BalanceArgs);
|
||||
tauri_command!(buy_xmr, BuyXmrArgs);
|
||||
tauri_command!(resume_swap, ResumeSwapArgs);
|
||||
tauri_command!(withdraw_btc, WithdrawBtcArgs);
|
||||
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
||||
tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args);
|
||||
tauri_command!(get_history, GetHistoryArgs, no_args);
|
||||
/// Represents the shared Tauri state. It is accessed by Tauri commands
|
||||
struct State {
|
||||
pub context: Option<Arc<Context>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Creates a new State instance with no Context
|
||||
fn new() -> Self {
|
||||
Self { context: None }
|
||||
}
|
||||
|
||||
/// Sets the context for the application state
|
||||
/// This is typically called after the Context has been initialized
|
||||
/// in the setup function
|
||||
fn set_context(&mut self, context: impl Into<Option<Arc<Context>>>) {
|
||||
self.context = context.into();
|
||||
}
|
||||
|
||||
/// Attempts to retrieve the context
|
||||
/// Returns an error if the context is not available
|
||||
fn try_get_context(&self) -> Result<Arc<Context>, String> {
|
||||
self.context
|
||||
.clone()
|
||||
.ok_or("Context not available")
|
||||
.to_string_result()
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets up the Tauri application
|
||||
/// Initializes the Tauri state and spawns an async task to set up the Context
|
||||
fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
tauri::async_runtime::block_on(async {
|
||||
let app_handle = app.app_handle().to_owned();
|
||||
|
||||
// We need to set a value for the Tauri state right at the start
|
||||
// If we don't do this, Tauri commands will panic at runtime if no value is present
|
||||
app_handle.manage::<RwLock<State>>(RwLock::new(State::new()));
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
let tauri_handle = TauriHandle::new(app_handle.clone());
|
||||
|
||||
let context = ContextBuilder::new(true)
|
||||
.with_bitcoin(Bitcoin {
|
||||
bitcoin_electrum_rpc_url: None,
|
||||
|
@ -99,11 +130,26 @@ fn setup(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
|||
})
|
||||
.with_json(false)
|
||||
.with_debug(true)
|
||||
.with_tauri(TauriHandle::new(app.app_handle().to_owned()))
|
||||
.with_tauri(tauri_handle.clone())
|
||||
.build()
|
||||
.await
|
||||
.expect("failed to create context");
|
||||
app.manage(Arc::new(context));
|
||||
.await;
|
||||
|
||||
match context {
|
||||
Ok(context) => {
|
||||
let state = app_handle.state::<RwLock<State>>();
|
||||
|
||||
state.write().await.set_context(Arc::new(context));
|
||||
|
||||
// To display to the user that the setup is done, we emit an event to the Tauri frontend
|
||||
tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Available);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error while initializing context: {:?}", e);
|
||||
|
||||
// To display to the user that the setup failed, we emit an event to the Tauri frontend
|
||||
tauri_handle.emit_context_init_progress_event(TauriContextStatusEvent::Failed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
|
@ -127,12 +173,35 @@ pub fn run() {
|
|||
.expect("error while building tauri application")
|
||||
.run(|app, event| match event {
|
||||
RunEvent::Exit | RunEvent::ExitRequested { .. } => {
|
||||
let context = app.state::<Arc<Context>>().inner();
|
||||
// Here we cleanup the Context when the application is closed
|
||||
// This is necessary to among other things stop the monero-wallet-rpc process
|
||||
// If the application is forcibly closed, this may not be called
|
||||
let context = app.state::<RwLock<State>>().inner().try_read();
|
||||
|
||||
if let Err(err) = context.cleanup() {
|
||||
println!("Cleanup failed {}", err);
|
||||
match context {
|
||||
Ok(context) => {
|
||||
if let Some(context) = context.context.as_ref() {
|
||||
if let Err(err) = context.cleanup() {
|
||||
println!("Cleanup failed {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
println!("Failed to acquire lock on context: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
}
|
||||
|
||||
// Here we define the Tauri commands that will be available to the frontend
|
||||
// The commands are defined using the `tauri_command!` macro.
|
||||
// Implementations are handled by the Request trait
|
||||
tauri_command!(get_balance, BalanceArgs);
|
||||
tauri_command!(buy_xmr, BuyXmrArgs);
|
||||
tauri_command!(resume_swap, ResumeSwapArgs);
|
||||
tauri_command!(withdraw_btc, WithdrawBtcArgs);
|
||||
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
|
||||
tauri_command!(get_swap_infos_all, GetSwapInfosAllArgs, no_args);
|
||||
tauri_command!(get_history, GetHistoryArgs, no_args);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue