diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb4df34..0b83d64b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Removed ### Fixed +- [\#164](https://github.com/Manta-Network/manta-signer/pull/164) Adding some communication between UI and backend to ensure sync at connection start ### Security diff --git a/ui/src-tauri/rust-toolchain b/ui/src-tauri/rust-toolchain deleted file mode 100644 index 2bf5ad04..00000000 --- a/ui/src-tauri/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -stable diff --git a/ui/src-tauri/src/main.rs b/ui/src-tauri/src/main.rs index 881d5c9f..65c2cfcd 100644 --- a/ui/src-tauri/src/main.rs +++ b/ui/src-tauri/src/main.rs @@ -26,7 +26,10 @@ extern crate alloc; -use core::time::Duration; +use core::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; use manta_signer::{ config::{Config, Setup}, secret::{ @@ -36,13 +39,64 @@ use manta_signer::{ serde::Serialize, service::Server, storage::Store, - tokio::time::sleep, }; +use std::time::Instant; use tauri::{ async_runtime::spawn, CustomMenuItem, Manager, RunEvent, Runtime, State, SystemTray, SystemTrayEvent, SystemTrayMenu, Window, WindowEvent, }; +/// App State +/// +/// Keeps track of global state flags that we need for specific behaviors. +#[derive(Debug)] +pub struct AppState { + /// UI is Connected + pub ui_connected: AtomicBool, +} + +impl AppState { + /// Builds a new [`AppState`]. + #[inline] + pub const fn new() -> Self { + Self { + ui_connected: AtomicBool::new(false), + } + } + + /// Returns the UI connection status. + #[inline] + pub fn get_ui_connected(&self) -> bool { + self.ui_connected.load(Ordering::Relaxed) + } + + /// Sets the UI connection status. + #[inline] + pub fn set_ui_connected(&self, ui_connected: bool) { + self.ui_connected.store(ui_connected, Ordering::Relaxed) + } +} + +/// Application State +pub static APP_STATE: AppState = AppState::new(); + +/// Repeatedly executes `f` until the `timeout` is reached calling `exit` to return from the +/// function. +#[inline] +pub fn while_timeout(timeout: Duration, mut f: F, exit: E) -> T +where + F: FnMut(), + E: FnOnce(Instant, Duration) -> T, +{ + let time_start = Instant::now(); + loop { + f(); + if time_start.elapsed() >= timeout { + return exit(time_start, timeout); + } + } +} + /// User pub struct User { /// Main Window @@ -107,11 +161,23 @@ impl Authorizer for User { fn setup<'s>(&'s mut self, setup: &'s Setup) -> UnitFuture<'s> { let window = self.window.clone(); Box::pin(async move { - // NOTE: We have to wait here until the UI listener is registered. - sleep(Duration::from_millis(500)).await; - window - .emit("connect", setup) - .expect("The `connect` command failed to be emitted to the window."); + while_timeout( + Duration::from_millis(5000), + move || { + if APP_STATE.get_ui_connected() { + return; + } + window + .emit("connect", setup) + .expect("The `connect` command failed to be emitted to the window."); + }, + move |time_start, timeout| { + panic!( + "Connection attempt timed-out! Started: {:?} with {:?} timeout.", + time_start, timeout + ); + }, + ) }) } @@ -136,6 +202,16 @@ pub type PasswordStore = Store; /// Server Store pub type ServerStore = Store>; +/// Called from the UI after it recieves a `connect` event. +/// +/// To ensure proper connection you should emit `connect` continuously until the +/// [`AppState::ui_connected`] flag is `true` then stop. This is the only way for now to ensure they +/// are synchronized. Tauri is working on a better way. +#[tauri::command] +fn ui_connected() { + APP_STATE.set_ui_connected(true); +} + /// Sends the current `password` into storage from the UI. #[tauri::command] async fn send_password( @@ -220,6 +296,7 @@ fn main() { .invoke_handler(tauri::generate_handler![ send_password, stop_password_prompt, + ui_connected, ]) .build(tauri::generate_context!()) .expect("Error while building UI."); diff --git a/ui/src/App.js b/ui/src/App.js index 4049d0d0..c312966f 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -24,6 +24,7 @@ function App() { if (isConnected) return; const beginInitialConnectionPhase = async () => { await once('connect', (event) => { + invoke('ui_connected'); console.log("[INFO]: Connect Event: ", event); let payload = event.payload; switch (payload.type) {