aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/ui/src/models/auth.ts
diff options
context:
space:
mode:
author苏向夜 <46275354+fu050409@users.noreply.github.com>2026-03-26 09:02:10 +0800
committerGitHub <noreply@github.com>2026-03-26 09:02:10 +0800
commit94b0d8e208363c802c12b56d8bdbef574dd1fb91 (patch)
treee86c8d46e73262c67c1755aaf4202cbcd1f8f844 /packages/ui/src/models/auth.ts
parent7d0e92e6d3b172adfe552ffae9b97f8dad6f63ae (diff)
parent3a31d3004b2814cd8a26d49a0f8a96636411dcd2 (diff)
downloadDropOut-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.ts87
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,