Files
friendolls-desktop/src-tauri/src/app.rs

126 lines
3.6 KiB
Rust

use reqwest::StatusCode;
use std::time::Duration;
use tokio::time::{sleep, Instant};
use tracing::{info, warn};
use crate::{
lock_w,
remotes::health::{HealthError, HealthRemote},
services::{
auth::{get_access_token, get_tokens},
health_manager::show_health_manager_with_error,
scene::{close_splash_window, open_scene_window, open_splash_window},
welcome::open_welcome_window,
ws::init_ws_client,
},
state::init_app_data,
system_tray::{init_system_tray, update_system_tray},
FDOLL,
};
pub async fn start_fdoll() {
let tray = init_system_tray();
{
let mut guard = lock_w!(FDOLL);
guard.tray = Some(tray);
}
if let Err(err) = init_startup_sequence().await {
tracing::error!("startup sequence failed: {err}");
show_health_manager_with_error(Some(err.to_string()));
}
}
async fn init_ws_after_auth() {
const MAX_ATTEMPTS: u8 = 5;
const BACKOFF: Duration = Duration::from_millis(300);
for _attempt in 1..=MAX_ATTEMPTS {
if get_access_token().await.is_some() {
init_ws_client().await;
return;
}
sleep(BACKOFF).await;
}
}
async fn construct_app() {
open_splash_window();
// Record start time for minimum splash duration
let start = Instant::now();
// Initialize app data first so we only start WebSocket after auth is fully available
init_app_data().await;
// Initialize WebSocket client after we know auth is present
init_ws_after_auth().await;
// Ensure splash stays visible for at least 3 seconds
let elapsed = start.elapsed();
if elapsed < Duration::from_secs(3) {
sleep(Duration::from_secs(3) - elapsed).await;
}
// Close splash and open main scene
close_splash_window();
open_scene_window();
}
pub async fn bootstrap() {
match get_tokens().await {
Some(_tokens) => {
info!("Tokens found in keyring - restoring user session");
construct_app().await;
update_system_tray(true);
}
None => {
info!("No active session found - showing welcome first");
open_welcome_window();
close_splash_window();
update_system_tray(false);
}
}
}
/// Perform checks for environment, network condition
/// and handle situations where startup would not be appropriate.
async fn init_startup_sequence() -> Result<(), HealthError> {
let health_remote = HealthRemote::try_new()?;
// simple retry loop to smooth transient network issues
const MAX_ATTEMPTS: u8 = 3;
const BACKOFF_MS: u64 = 500;
for attempt in 1..=MAX_ATTEMPTS {
match health_remote.get_health().await {
Ok(_) => {
bootstrap().await;
return Ok(());
}
Err(HealthError::NonOkStatus(status)) => {
warn!(attempt, "server health reported non-OK status: {status}");
return Err(HealthError::NonOkStatus(status));
}
Err(HealthError::UnexpectedStatus(status)) => {
warn!(attempt, "server health check failed with status: {status}");
return Err(HealthError::UnexpectedStatus(status));
}
Err(err) => {
warn!(attempt, "server health check failed: {err}");
if attempt == MAX_ATTEMPTS {
return Err(err);
}
}
}
if attempt < MAX_ATTEMPTS {
sleep(Duration::from_millis(BACKOFF_MS)).await;
}
}
Err(HealthError::UnexpectedStatus(
StatusCode::SERVICE_UNAVAILABLE,
))
}