From f49685f30fe93f105ad4bde61d639f6e0a5c2c0f Mon Sep 17 00:00:00 2001 From: HsiangNianian Date: Tue, 13 Jan 2026 18:22:49 +0800 Subject: feat: implement Microsoft account login flow and refactor account handling --- ui/src/App.svelte | 170 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 150 insertions(+), 20 deletions(-) (limited to 'ui') diff --git a/ui/src/App.svelte b/ui/src/App.svelte index d9dd7d0..8ec0da1 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -25,11 +25,19 @@ releaseTime: string; } - interface OfflineAccount { + interface Account { + type: "Offline" | "Microsoft"; username: string; uuid: string; } + interface DeviceCodeResponse { + user_code: string; + device_code: string; + verification_uri: string; + message?: string; + } + interface LauncherConfig { min_memory: number; max_memory: number; @@ -40,7 +48,7 @@ let versions: Version[] = []; let selectedVersion = ""; - let currentAccount: OfflineAccount | null = null; + let currentAccount: Account | null = null; let settings: LauncherConfig = { min_memory: 1024, max_memory: 2048, @@ -49,6 +57,13 @@ height: 480, }; + // Login UI State + let isLoginModalOpen = false; + let loginMode: 'select' | 'offline' | 'microsoft' = 'select'; + let offlineUsername = ""; + let deviceCodeData: DeviceCodeResponse | null = null; + let msLoginLoading = false; + onMount(async () => { checkAccount(); loadSettings(); @@ -68,7 +83,7 @@ async function checkAccount() { try { const acc = await invoke("get_active_account"); - currentAccount = acc as OfflineAccount | null; + currentAccount = acc as Account | null; } catch (e) { console.error("Failed to check account:", e); } @@ -92,32 +107,75 @@ } } - async function login() { + // --- Auth Functions --- + + function openLoginModal() { if (currentAccount) { if (confirm("Logout " + currentAccount.username + "?")) { - try { - await invoke("logout"); - currentAccount = null; - } catch (e) { - console.error("Logout failed:", e); - } + invoke("logout").then(() => currentAccount = null); } return; } - const username = prompt("Enter username for offline login:"); - if (username) { - try { - currentAccount = await invoke("login_offline", { username }); - } catch (e) { - alert("Login failed: " + e); - } + // Reset state + isLoginModalOpen = true; + loginMode = 'select'; + offlineUsername = ""; + deviceCodeData = null; + msLoginLoading = false; + } + + function closeLoginModal() { + isLoginModalOpen = false; + } + + async function performOfflineLogin() { + if (!offlineUsername) return; + try { + currentAccount = await invoke("login_offline", { username: offlineUsername }) as Account; + isLoginModalOpen = false; + } catch (e) { + alert("Login failed: " + e); + } + } + + async function startMicrosoftLogin() { + loginMode = 'microsoft'; + msLoginLoading = true; + try { + deviceCodeData = await invoke("start_microsoft_login") as DeviceCodeResponse; + } catch(e) { + alert("Failed to start Microsoft login: " + e); + loginMode = 'select'; // Go back + } finally { + msLoginLoading = false; } } + + async function completeMicrosoftLogin() { + if(!deviceCodeData) return; + msLoginLoading = true; + try { + currentAccount = await invoke("complete_microsoft_login", { deviceCode: deviceCodeData.device_code }) as Account; + isLoginModalOpen = false; + } catch(e) { + alert("Login failed: " + e + "\n\nMake sure you authorized the app in your browser."); + } finally { + msLoginLoading = false; + } + } + + function openLink(url: string) { + // Use tauri open if possible, or window.open + invoke('start_microsoft_login').catch(() => window.open(url, '_blank')); + // Wait, we invoke 'start_microsoft_login' above already. + // Just use window.open for the verification URI + window.open(url, '_blank'); + } async function startGame() { if (!currentAccount) { alert("Please login first!"); - login(); + openLoginModal(); return; } @@ -359,10 +417,10 @@
e.key === "Enter" && login()} + onkeydown={(e) => e.key === "Enter" && openLoginModal()} >
+ + {#if isLoginModalOpen} +
+
+ +
+

Login

+ +
+ + {#if loginMode === 'select'} +
+ + +
+
+
OR
+
+ +
+ e.key === 'Enter' && performOfflineLogin()} /> + +
+
+ + {:else if loginMode === 'microsoft'} +
+ {#if msLoginLoading && !deviceCodeData} +
Starting login flow...
+ {:else if deviceCodeData} +
+

1. Go to this URL:

+ + +

2. Enter this code:

+
navigator.clipboard.writeText(deviceCodeData?.user_code || '')}> + {deviceCodeData.user_code} +
+

Click code to copy

+ +
+ {#if msLoginLoading} + + {:else} + + {/if} +
+ + +
+ {/if} +
+ {/if} +
+
+ {/if} + {#if status !== "Ready"}