diff options
| author | 2026-03-26 09:02:10 +0800 | |
|---|---|---|
| committer | 2026-03-26 09:02:10 +0800 | |
| commit | 94b0d8e208363c802c12b56d8bdbef574dd1fb91 (patch) | |
| tree | e86c8d46e73262c67c1755aaf4202cbcd1f8f844 /packages/ui/src/models/auth.ts | |
| parent | 7d0e92e6d3b172adfe552ffae9b97f8dad6f63ae (diff) | |
| parent | 3a31d3004b2814cd8a26d49a0f8a96636411dcd2 (diff) | |
| download | DropOut-94b0d8e208363c802c12b56d8bdbef574dd1fb91.tar.gz DropOut-94b0d8e208363c802c12b56d8bdbef574dd1fb91.zip | |
Add game lifecycle management and instance import/export tools (#117)
## Summary by Sourcery
Add centralized game process and instance lifecycle management, shared
cache-aware path resolution, and instance import/export/repair
capabilities across backend and UI.
New Features:
- Track a single running game process in the backend, expose stop-game
control, and emit structured game-exited events with instance and
version context.
- Introduce instance path resolution that supports shared caches for
versions, libraries, and assets, and use it across game start, install,
and version management APIs.
- Add import, export, and repair operations for instances, including
zip-based archive support and automatic recovery of on-disk instances.
- Expose new instance lifecycle and repair APIs to the frontend and wire
them through the client and instance store.
- Add per-instance start/stop controls in the instances view and
instance selection in the bottom bar for launching games.
Enhancements:
- Guard instance operations with per-instance locks and track active
operations such as launch, install, delete, and import/export.
- Improve handling of Microsoft login errors and polling status, with
clearer user feedback and safer interval management.
- Simplify config mutation during shared cache migration and centralize
instance directory resolution in the backend.
- Initialize a game lifecycle listener at app startup to keep UI state
in sync with backend game exit events.
Build:
- Configure the Vite dev server to use a fixed localhost host and port
for the UI dev environment.
Diffstat (limited to 'packages/ui/src/models/auth.ts')
| -rw-r--r-- | packages/ui/src/models/auth.ts | 87 |
1 files changed, 67 insertions, 20 deletions
diff --git a/packages/ui/src/models/auth.ts b/packages/ui/src/models/auth.ts index 10b2a0d..9c814d2 100644 --- a/packages/ui/src/models/auth.ts +++ b/packages/ui/src/models/auth.ts @@ -13,6 +13,10 @@ import { } from "@/client"; import type { Account, DeviceCodeResponse } from "@/types"; +function getAuthErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + export interface AuthState { account: Account | null; loginMode: Account["type"] | null; @@ -68,36 +72,78 @@ export const useAuthStore = create<AuthState>((set, get) => ({ toast.warning("Failed to attch auth-progress listener"); } - const deviceCode = await startMicrosoftLogin(); - navigator.clipboard?.writeText(deviceCode.userCode).catch((err) => { - console.error("Failed to copy to clipboard:", err); - }); - open(deviceCode.verificationUri).catch((err) => { - console.error("Failed to open browser:", err); - }); - const ms = Number(deviceCode.interval) * 1000; - const interval = setInterval(() => { - _pollLoginStatus(deviceCode.deviceCode, onSuccess); - }, ms); - set({ _pollingInterval: interval, deviceCode }); + try { + const deviceCode = await startMicrosoftLogin(); + + navigator.clipboard?.writeText(deviceCode.userCode).catch((err) => { + console.error("Failed to copy to clipboard:", err); + }); + open(deviceCode.verificationUri).catch((err) => { + console.error("Failed to open browser:", err); + }); + + const ms = Math.max(1, Number(deviceCode.interval) || 5) * 1000; + const interval = setInterval(() => { + _pollLoginStatus(deviceCode.deviceCode, onSuccess); + }, ms); + + set({ + _pollingInterval: interval, + deviceCode, + statusMessage: deviceCode.message ?? "Waiting for authorization...", + }); + } catch (error) { + const message = getAuthErrorMessage(error); + console.error("Failed to start Microsoft login:", error); + set({ loginMode: null, statusMessage: `Failed to start login: ${message}` }); + toast.error(`Failed to start Microsoft login: ${message}`); + } }, _pollLoginStatus: async (deviceCode, onSuccess) => { const { _pollingInterval, _mutex: mutex, _progressUnlisten } = get(); if (mutex.isLocked) return; - mutex.acquire(); + + await mutex.acquire(); + try { const account = await completeMicrosoftLogin(deviceCode); clearInterval(_pollingInterval ?? undefined); _progressUnlisten?.(); onSuccess?.(); - set({ account, loginMode: "microsoft" }); - } catch (error) { - if (error === "authorization_pending") { - console.log("Authorization pending..."); - } else { - console.error("Failed to poll login status:", error); - toast.error("Failed to poll login status"); + set({ + account, + loginMode: "microsoft", + deviceCode: null, + _pollingInterval: null, + _progressUnlisten: null, + statusMessage: "Login successful", + }); + } catch (error: unknown) { + const message = getAuthErrorMessage(error); + + if (message.includes("authorization_pending")) { + set({ statusMessage: "Waiting for authorization..." }); + return; + } + + if (message.includes("slow_down")) { + set({ statusMessage: "Microsoft asked to slow down polling..." }); + return; } + + clearInterval(_pollingInterval ?? undefined); + _progressUnlisten?.(); + + set({ + loginMode: null, + deviceCode: null, + _pollingInterval: null, + _progressUnlisten: null, + statusMessage: `Login failed: ${message}`, + }); + + console.error("Failed to poll login status:", error); + toast.error(`Microsoft login failed: ${message}`); } finally { mutex.release(); } @@ -111,6 +157,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({ } set({ loginMode: null, + deviceCode: null, _pollingInterval: null, statusMessage: null, _progressUnlisten: null, |