Skip to content

Commit

Permalink
Adding some communication between UI and backend to ensure sync at co…
Browse files Browse the repository at this point in the history
…nnection start (#164)

* Adding invoke to track when front end is ready
* Format
* comments
* Add panic! error message
* Fixing linux, this is a better fix as Tauri is really bad at syncing at the start
* Update comments
* Adding App State
* Update CHANGELOG.md
* fix: replace macro with function and add docs
* fix: move CHANGELOG entry to fix

Signed-off-by: Apokalip <simeon@manta.network>
Signed-off-by: Simeon Zahariev <43317481+Apokalip@users.noreply.github.com>
Signed-off-by: Brandon H. Gomes <bhgomes@pm.me>
Co-authored-by: Brandon H. Gomes <bhgomes@pm.me>
  • Loading branch information
Apokalip and bhgomes authored Aug 24, 2022
1 parent ac7466b commit 30b8780
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 0 additions & 1 deletion ui/src-tauri/rust-toolchain

This file was deleted.

91 changes: 84 additions & 7 deletions ui/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand All @@ -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<F, E, T>(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
Expand Down Expand Up @@ -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
);
},
)
})
}

Expand All @@ -136,6 +202,16 @@ pub type PasswordStore = Store<PasswordSender>;
/// Server Store
pub type ServerStore = Store<Server<User>>;

/// 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(
Expand Down Expand Up @@ -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.");
Expand Down
1 change: 1 addition & 0 deletions ui/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

1 comment on commit 30b8780

@vercel
Copy link

@vercel vercel bot commented on 30b8780 Aug 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.