aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src-tauri
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2026-01-14 15:54:39 +0800
committerGitHub <noreply@github.com>2026-01-14 15:54:39 +0800
commitce4b0c2053d5d16f7091d74840d4a502401f1a4e (patch)
tree170667359ecb773800cd334e4176341cf09306b2 /src-tauri
parentfd6d7ffef4a1c6b093ad1d5b83579ab27ef5327e (diff)
parent505e3485f3dfa31969651f7f281fde33e9843fe8 (diff)
downloadDropOut-ce4b0c2053d5d16f7091d74840d4a502401f1a4e.tar.gz
DropOut-ce4b0c2053d5d16f7091d74840d4a502401f1a4e.zip
Merge pull request #25 from HsiangNianian/main
Diffstat (limited to 'src-tauri')
-rw-r--r--src-tauri/src/core/account_storage.rs29
-rw-r--r--src-tauri/src/core/auth.rs36
-rw-r--r--src-tauri/src/core/downloader.rs40
-rw-r--r--src-tauri/src/core/java.rs24
-rw-r--r--src-tauri/src/main.rs187
5 files changed, 207 insertions, 109 deletions
diff --git a/src-tauri/src/core/account_storage.rs b/src-tauri/src/core/account_storage.rs
index b8e15e1..569df7b 100644
--- a/src-tauri/src/core/account_storage.rs
+++ b/src-tauri/src/core/account_storage.rs
@@ -4,21 +4,12 @@ use std::fs;
use std::path::PathBuf;
/// Stored account data for persistence
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct AccountStore {
pub accounts: Vec<StoredAccount>,
pub active_account_id: Option<String>,
}
-impl Default for AccountStore {
- fn default() -> Self {
- Self {
- accounts: Vec::new(),
- active_account_id: None,
- }
- }
-}
-
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum StoredAccount {
@@ -131,13 +122,17 @@ impl AccountStorage {
pub fn get_active_account(&self) -> Option<(StoredAccount, Option<String>)> {
let store = self.load();
if let Some(active_id) = &store.active_account_id {
- store.accounts.iter().find(|a| &a.id() == active_id).map(|a| {
- let ms_token = match a {
- StoredAccount::Microsoft(m) => m.ms_refresh_token.clone(),
- _ => None,
- };
- (a.clone(), ms_token)
- })
+ store
+ .accounts
+ .iter()
+ .find(|a| &a.id() == active_id)
+ .map(|a| {
+ let ms_token = match a {
+ StoredAccount::Microsoft(m) => m.ms_refresh_token.clone(),
+ _ => None,
+ };
+ (a.clone(), ms_token)
+ })
} else {
None
}
diff --git a/src-tauri/src/core/auth.rs b/src-tauri/src/core/auth.rs
index 624f1de..5f01a58 100644
--- a/src-tauri/src/core/auth.rs
+++ b/src-tauri/src/core/auth.rs
@@ -2,7 +2,6 @@ use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use uuid::Uuid;
-
// Helper to create a client with a custom User-Agent
// This is critical because Microsoft's WAF often blocks requests without a valid UA
fn get_client() -> reqwest::Client {
@@ -116,7 +115,7 @@ pub async fn refresh_microsoft_token(refresh_token: &str) -> Result<TokenRespons
let resp = client
.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
- .body(serde_urlencoded::to_string(&params).map_err(|e| e.to_string())?)
+ .body(serde_urlencoded::to_string(params).map_err(|e| e.to_string())?)
.send()
.await
.map_err(|e| e.to_string())?;
@@ -142,30 +141,32 @@ pub fn is_token_expired(expires_at: i64) -> bool {
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
-
+
// Consider expired if less than 5 minutes remaining
expires_at - now < 300
}
/// Full refresh flow: refresh MS token -> Xbox -> XSTS -> Minecraft
-pub async fn refresh_full_auth(ms_refresh_token: &str) -> Result<(MicrosoftAccount, String), String> {
+pub async fn refresh_full_auth(
+ ms_refresh_token: &str,
+) -> Result<(MicrosoftAccount, String), String> {
println!("[Auth] Starting full token refresh...");
-
+
// 1. Refresh Microsoft token
let token_resp = refresh_microsoft_token(ms_refresh_token).await?;
-
+
// 2. Xbox Live Auth
let (xbl_token, uhs) = method_xbox_live(&token_resp.access_token).await?;
-
+
// 3. XSTS Auth
let xsts_token = method_xsts(&xbl_token).await?;
-
+
// 4. Minecraft Auth
let mc_token = login_minecraft(&xsts_token, &uhs).await?;
-
+
// 5. Get Profile
let profile = fetch_profile(&mc_token).await?;
-
+
// 6. Create Account
let account = MicrosoftAccount {
username: profile.name,
@@ -175,12 +176,15 @@ pub async fn refresh_full_auth(ms_refresh_token: &str) -> Result<(MicrosoftAccou
expires_at: (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
- .as_secs() + token_resp.expires_in) as i64,
+ .as_secs()
+ + token_resp.expires_in) as i64,
};
-
+
// Return new MS refresh token for storage
- let new_ms_refresh = token_resp.refresh_token.unwrap_or_else(|| ms_refresh_token.to_string());
-
+ let new_ms_refresh = token_resp
+ .refresh_token
+ .unwrap_or_else(|| ms_refresh_token.to_string());
+
Ok((account, new_ms_refresh))
}
@@ -221,7 +225,7 @@ pub async fn start_device_flow() -> Result<DeviceCodeResponse, String> {
let resp = client
.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
- .body(serde_urlencoded::to_string(&params).map_err(|e| e.to_string())?)
+ .body(serde_urlencoded::to_string(params).map_err(|e| e.to_string())?)
.send()
.await
.map_err(|e| e.to_string())?;
@@ -257,7 +261,7 @@ pub async fn exchange_code_for_token(device_code: &str) -> Result<TokenResponse,
let resp = client
.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
- .body(serde_urlencoded::to_string(&params).map_err(|e| e.to_string())?)
+ .body(serde_urlencoded::to_string(params).map_err(|e| e.to_string())?)
.send()
.await
.map_err(|e| e.to_string())?;
diff --git a/src-tauri/src/core/downloader.rs b/src-tauri/src/core/downloader.rs
index 7ff81ad..3add9b7 100644
--- a/src-tauri/src/core/downloader.rs
+++ b/src-tauri/src/core/downloader.rs
@@ -69,7 +69,10 @@ impl GlobalProgress {
/// Add downloaded bytes and return updated snapshot
fn add_bytes(&self, delta: u64) -> ProgressSnapshot {
- let total_bytes = self.total_downloaded_bytes.fetch_add(delta, Ordering::Relaxed) + delta;
+ let total_bytes = self
+ .total_downloaded_bytes
+ .fetch_add(delta, Ordering::Relaxed)
+ + delta;
ProgressSnapshot {
completed_files: self.completed_files.load(Ordering::Relaxed),
total_files: self.total_files,
@@ -101,10 +104,14 @@ fn emit_progress(
);
}
-pub async fn download_files(window: Window, tasks: Vec<DownloadTask>, max_concurrent: usize) -> Result<(), String> {
+pub async fn download_files(
+ window: Window,
+ tasks: Vec<DownloadTask>,
+ max_concurrent: usize,
+) -> Result<(), String> {
// Clamp max_concurrent to a valid range (1-128) to prevent edge cases
let max_concurrent = max_concurrent.clamp(1, 128);
-
+
let client = reqwest::Client::new();
let semaphore = Arc::new(Semaphore::new(max_concurrent));
let progress = Arc::new(GlobalProgress::new(tasks.len()));
@@ -141,7 +148,14 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>, max_concur
if skipped_size > 0 {
let _ = progress.add_bytes(skipped_size);
}
- emit_progress(&window, &file_name, "Skipped", 0, 0, &progress.inc_completed());
+ emit_progress(
+ &window,
+ &file_name,
+ "Skipped",
+ 0,
+ 0,
+ &progress.inc_completed(),
+ );
return Ok(());
}
}
@@ -170,7 +184,14 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>, max_concur
}
downloaded += chunk.len() as u64;
let snapshot = progress.add_bytes(chunk.len() as u64);
- emit_progress(&window, &file_name, "Downloading", downloaded, total_size, &snapshot);
+ emit_progress(
+ &window,
+ &file_name,
+ "Downloading",
+ downloaded,
+ total_size,
+ &snapshot,
+ );
}
Ok(None) => break,
Err(e) => return Err(format!("Download error: {}", e)),
@@ -180,7 +201,14 @@ pub async fn download_files(window: Window, tasks: Vec<DownloadTask>, max_concur
Err(e) => return Err(format!("Request error: {}", e)),
}
- emit_progress(&window, &file_name, "Finished", 0, 0, &progress.inc_completed());
+ emit_progress(
+ &window,
+ &file_name,
+ "Finished",
+ 0,
+ 0,
+ &progress.inc_completed(),
+ );
Ok(())
}
});
diff --git a/src-tauri/src/core/java.rs b/src-tauri/src/core/java.rs
index e0962fa..9cf3053 100644
--- a/src-tauri/src/core/java.rs
+++ b/src-tauri/src/core/java.rs
@@ -17,7 +17,10 @@ pub fn detect_java_installations() -> Vec<JavaInstallation> {
for candidate in candidates {
if let Some(java) = check_java_installation(&candidate) {
// Avoid duplicates
- if !installations.iter().any(|j: &JavaInstallation| j.path == java.path) {
+ if !installations
+ .iter()
+ .any(|j: &JavaInstallation| j.path == java.path)
+ {
installations.push(java);
}
}
@@ -121,7 +124,9 @@ fn get_java_candidates() -> Vec<PathBuf> {
if homebrew_arm.exists() {
if let Ok(entries) = std::fs::read_dir(&homebrew_arm) {
for entry in entries.flatten() {
- let java_path = entry.path().join("libexec/openjdk.jdk/Contents/Home/bin/java");
+ let java_path = entry
+ .path()
+ .join("libexec/openjdk.jdk/Contents/Home/bin/java");
if java_path.exists() {
candidates.push(java_path);
}
@@ -133,8 +138,10 @@ fn get_java_candidates() -> Vec<PathBuf> {
#[cfg(target_os = "windows")]
{
// Windows Java paths
- let program_files = std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
- let program_files_x86 = std::env::var("ProgramFiles(x86)").unwrap_or_else(|_| "C:\\Program Files (x86)".to_string());
+ let program_files =
+ std::env::var("ProgramFiles").unwrap_or_else(|_| "C:\\Program Files".to_string());
+ let program_files_x86 = std::env::var("ProgramFiles(x86)")
+ .unwrap_or_else(|_| "C:\\Program Files (x86)".to_string());
let local_app_data = std::env::var("LOCALAPPDATA").unwrap_or_default();
let win_paths = [
@@ -186,14 +193,11 @@ fn get_java_candidates() -> Vec<PathBuf> {
/// Check a specific Java installation and get its version info
fn check_java_installation(path: &PathBuf) -> Option<JavaInstallation> {
- let output = Command::new(path)
- .arg("-version")
- .output()
- .ok()?;
+ let output = Command::new(path).arg("-version").output().ok()?;
// Java outputs version info to stderr
let version_output = String::from_utf8_lossy(&output.stderr);
-
+
// Parse version string (e.g., "openjdk version \"17.0.1\"" or "java version \"1.8.0_301\"")
let version = parse_version_string(&version_output)?;
let is_64bit = version_output.contains("64-Bit");
@@ -240,7 +244,7 @@ fn parse_java_version(version: &str) -> u32 {
/// Get the best Java for a specific Minecraft version
pub fn get_recommended_java(required_major_version: Option<u64>) -> Option<JavaInstallation> {
let installations = detect_java_installations();
-
+
if let Some(required) = required_major_version {
// Find exact match or higher
installations.into_iter().find(|java| {
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 73310d5..ae74a03 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -26,6 +26,12 @@ pub struct MsRefreshTokenState {
pub token: Mutex<Option<String>>,
}
+impl Default for MsRefreshTokenState {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
impl MsRefreshTokenState {
pub fn new() -> Self {
Self {
@@ -41,7 +47,10 @@ async fn start_game(
config_state: State<'_, core::config::ConfigState>,
version_id: String,
) -> Result<String, String> {
- emit_log!(window, format!("Starting game launch for version: {}", version_id));
+ emit_log!(
+ window,
+ format!("Starting game launch for version: {}", version_id)
+ );
// Check for active account
emit_log!(window, "Checking for active account...".to_string());
@@ -51,16 +60,22 @@ async fn start_game(
.unwrap()
.clone()
.ok_or("No active account found. Please login first.")?;
-
+
let account_type = match &account {
core::auth::Account::Offline(_) => "Offline",
core::auth::Account::Microsoft(_) => "Microsoft",
};
- emit_log!(window, format!("Account found: {} ({})", account.username(), account_type));
+ emit_log!(
+ window,
+ format!("Account found: {} ({})", account.username(), account_type)
+ );
let config = config_state.config.lock().unwrap().clone();
emit_log!(window, format!("Java path: {}", config.java_path));
- emit_log!(window, format!("Memory: {}MB - {}MB", config.min_memory, config.max_memory));
+ emit_log!(
+ window,
+ format!("Memory: {}MB - {}MB", config.min_memory, config.max_memory)
+ );
// Get App Data Directory (e.g., ~/.local/share/com.dropout.launcher or similar)
// The identifier is set in tauri.conf.json.
@@ -83,7 +98,10 @@ async fn start_game(
let manifest = core::manifest::fetch_version_manifest()
.await
.map_err(|e| e.to_string())?;
- emit_log!(window, format!("Found {} versions in manifest", manifest.versions.len()));
+ emit_log!(
+ window,
+ format!("Found {} versions in manifest", manifest.versions.len())
+ );
// Find the version info
let version_info = manifest
@@ -93,7 +111,10 @@ async fn start_game(
.ok_or_else(|| format!("Version {} not found in manifest", version_id))?;
// 2. Fetch specific version JSON (client.jar info)
- emit_log!(window, format!("Fetching version details for {}...", version_id));
+ emit_log!(
+ window,
+ format!("Fetching version details for {}...", version_id)
+ );
let version_url = &version_info.url;
let version_details: core::game_version::GameVersion = reqwest::get(version_url)
.await
@@ -101,7 +122,13 @@ async fn start_game(
.json()
.await
.map_err(|e| e.to_string())?;
- emit_log!(window, format!("Version details loaded: main class = {}", version_details.main_class));
+ emit_log!(
+ window,
+ format!(
+ "Version details loaded: main class = {}",
+ version_details.main_class
+ )
+ );
// 3. Prepare download tasks
emit_log!(window, "Preparing download tasks...".to_string());
@@ -256,16 +283,29 @@ async fn start_game(
});
}
- emit_log!(window, format!(
- "Total download tasks: {} (Client + Libraries + Assets)",
- download_tasks.len()
- ));
+ emit_log!(
+ window,
+ format!(
+ "Total download tasks: {} (Client + Libraries + Assets)",
+ download_tasks.len()
+ )
+ );
// 4. Start Download
- emit_log!(window, format!("Starting downloads with {} concurrent threads...", config.download_threads));
- core::downloader::download_files(window.clone(), download_tasks, config.download_threads as usize)
- .await
- .map_err(|e| e.to_string())?;
+ emit_log!(
+ window,
+ format!(
+ "Starting downloads with {} concurrent threads...",
+ config.download_threads
+ )
+ );
+ core::downloader::download_files(
+ window.clone(),
+ download_tasks,
+ config.download_threads as usize,
+ )
+ .await
+ .map_err(|e| e.to_string())?;
emit_log!(window, "All downloads completed successfully".to_string());
// 5. Extract Natives
@@ -328,16 +368,16 @@ async fn start_game(
parse_jvm_arguments(jvm_args, &mut args, &natives_path, &classpath);
}
}
-
+
// Add memory settings (these override any defaults)
args.push(format!("-Xmx{}M", config.max_memory));
args.push(format!("-Xms{}M", config.min_memory));
-
+
// Ensure natives path is set if not already in jvm args
if !args.iter().any(|a| a.contains("-Djava.library.path")) {
args.push(format!("-Djava.library.path={}", natives_path));
}
-
+
// Ensure classpath is set if not already
if !args.iter().any(|a| a == "-cp" || a == "-classpath") {
args.push("-cp".to_string());
@@ -429,14 +469,20 @@ async fn start_game(
}
}
- emit_log!(window, format!("Preparing to launch game with {} arguments...", args.len()));
+ emit_log!(
+ window,
+ format!("Preparing to launch game with {} arguments...", args.len())
+ );
// Debug: Log arguments (only first few to avoid spam)
if args.len() > 10 {
emit_log!(window, format!("First 10 args: {:?}", &args[..10]));
}
// Spawn the process
- emit_log!(window, format!("Starting Java process: {}", config.java_path));
+ emit_log!(
+ window,
+ format!("Starting Java process: {}", config.java_path)
+ );
let mut command = Command::new(&config.java_path);
command.args(&args);
command.current_dir(&game_dir); // Run in game directory
@@ -448,7 +494,10 @@ async fn start_game(
{
const CREATE_NO_WINDOW: u32 = 0x08000000;
command.creation_flags(CREATE_NO_WINDOW);
- emit_log!(window, "Applied CREATE_NO_WINDOW flag for Windows".to_string());
+ emit_log!(
+ window,
+ "Applied CREATE_NO_WINDOW flag for Windows".to_string()
+ );
}
// Spawn and handle output
@@ -468,7 +517,10 @@ async fn start_game(
.expect("child did not have a handle to stderr");
// Emit launcher log that game is running
- emit_log!(window, "Game is now running, capturing output...".to_string());
+ emit_log!(
+ window,
+ "Game is now running, capturing output...".to_string()
+ );
let window_rx = window.clone();
tokio::spawn(async move {
@@ -537,9 +589,9 @@ fn parse_jvm_arguments(
} else if let Some(obj) = item.as_object() {
// Conditional argument with rules
let allow = if let Some(rules_val) = obj.get("rules") {
- if let Ok(rules) = serde_json::from_value::<Vec<core::game_version::Rule>>(
- rules_val.clone(),
- ) {
+ if let Ok(rules) =
+ serde_json::from_value::<Vec<core::game_version::Rule>>(rules_val.clone())
+ {
core::rules::is_library_allowed(&Some(rules))
} else {
false
@@ -596,13 +648,16 @@ async fn login_offline(
let account = core::auth::Account::Offline(core::auth::OfflineAccount { username, uuid });
*state.active_account.lock().unwrap() = Some(account.clone());
-
+
// Save to storage
let app_handle = window.app_handle();
- let app_dir = app_handle.path().app_data_dir().map_err(|e| e.to_string())?;
+ let app_dir = app_handle
+ .path()
+ .app_data_dir()
+ .map_err(|e| e.to_string())?;
let storage = core::account_storage::AccountStorage::new(app_dir);
storage.add_or_update_account(&account, None)?;
-
+
Ok(account)
}
@@ -614,23 +669,28 @@ async fn get_active_account(
}
#[tauri::command]
-async fn logout(
- window: Window,
- state: State<'_, core::auth::AccountState>,
-) -> Result<(), String> {
+async fn logout(window: Window, state: State<'_, core::auth::AccountState>) -> Result<(), String> {
// Get current account UUID before clearing
- let uuid = state.active_account.lock().unwrap().as_ref().map(|a| a.uuid());
-
+ let uuid = state
+ .active_account
+ .lock()
+ .unwrap()
+ .as_ref()
+ .map(|a| a.uuid());
+
*state.active_account.lock().unwrap() = None;
-
+
// Remove from storage
if let Some(uuid) = uuid {
let app_handle = window.app_handle();
- let app_dir = app_handle.path().app_data_dir().map_err(|e| e.to_string())?;
+ let app_dir = app_handle
+ .path()
+ .app_data_dir()
+ .map_err(|e| e.to_string())?;
let storage = core::account_storage::AccountStorage::new(app_dir);
storage.remove_account(&uuid)?;
}
-
+
Ok(())
}
@@ -665,23 +725,23 @@ async fn complete_microsoft_login(
) -> Result<core::auth::Account, String> {
// 1. Poll (once) for token
let token_resp = core::auth::exchange_code_for_token(&device_code).await?;
-
+
// Store MS refresh token
let ms_refresh_token = token_resp.refresh_token.clone();
*ms_refresh_state.token.lock().unwrap() = ms_refresh_token.clone();
-
+
// 2. Xbox Live Auth
let (xbl_token, uhs) = core::auth::method_xbox_live(&token_resp.access_token).await?;
-
+
// 3. XSTS Auth
let xsts_token = core::auth::method_xsts(&xbl_token).await?;
-
+
// 4. Minecraft Auth
let mc_token = core::auth::login_minecraft(&xsts_token, &uhs).await?;
-
+
// 5. Get Profile
let profile = core::auth::fetch_profile(&mc_token).await?;
-
+
// 6. Create Account
let account = core::auth::Account::Microsoft(core::auth::MicrosoftAccount {
username: profile.name,
@@ -691,18 +751,22 @@ async fn complete_microsoft_login(
expires_at: (std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
- .as_secs() + token_resp.expires_in) as i64,
+ .as_secs()
+ + token_resp.expires_in) as i64,
});
-
+
// 7. Save to state
*state.active_account.lock().unwrap() = Some(account.clone());
-
+
// 8. Save to storage
let app_handle = window.app_handle();
- let app_dir = app_handle.path().app_data_dir().map_err(|e| e.to_string())?;
+ let app_dir = app_handle
+ .path()
+ .app_data_dir()
+ .map_err(|e| e.to_string())?;
let storage = core::account_storage::AccountStorage::new(app_dir);
storage.add_or_update_account(&account, ms_refresh_token)?;
-
+
Ok(account)
}
@@ -715,26 +779,29 @@ async fn refresh_account(
) -> Result<core::auth::Account, String> {
// Get stored MS refresh token
let app_handle = window.app_handle();
- let app_dir = app_handle.path().app_data_dir().map_err(|e| e.to_string())?;
+ let app_dir = app_handle
+ .path()
+ .app_data_dir()
+ .map_err(|e| e.to_string())?;
let storage = core::account_storage::AccountStorage::new(app_dir.clone());
-
+
let (stored_account, ms_refresh) = storage
.get_active_account()
.ok_or("No active account found")?;
-
+
let ms_refresh_token = ms_refresh.ok_or("No refresh token available")?;
-
+
// Perform full refresh
let (new_account, new_ms_refresh) = core::auth::refresh_full_auth(&ms_refresh_token).await?;
let account = core::auth::Account::Microsoft(new_account);
-
+
// Update state
*state.active_account.lock().unwrap() = Some(account.clone());
*ms_refresh_state.token.lock().unwrap() = Some(new_ms_refresh.clone());
-
+
// Update storage
storage.add_or_update_account(&account, Some(new_ms_refresh))?;
-
+
Ok(account)
}
@@ -760,25 +827,25 @@ fn main() {
.setup(|app| {
let config_state = core::config::ConfigState::new(app.handle());
app.manage(config_state);
-
+
// Load saved account on startup
let app_dir = app.path().app_data_dir().unwrap();
let storage = core::account_storage::AccountStorage::new(app_dir);
-
+
if let Some((stored_account, ms_refresh)) = storage.get_active_account() {
let account = stored_account.to_account();
let auth_state: State<core::auth::AccountState> = app.state();
*auth_state.active_account.lock().unwrap() = Some(account);
-
+
// Store MS refresh token
if let Some(token) = ms_refresh {
let ms_state: State<MsRefreshTokenState> = app.state();
*ms_state.token.lock().unwrap() = Some(token);
}
-
+
println!("[Startup] Loaded saved account");
}
-
+
Ok(())
})
.invoke_handler(tauri::generate_handler![