Fix desktop auth gate after backend startup
This commit is contained in:
parent
1729454db0
commit
c23e91e009
2 changed files with 63 additions and 13 deletions
|
|
@ -9,12 +9,13 @@ import {
|
|||
shouldUseCustomWindowTitlebar,
|
||||
} from "@/components/tauri/window-titlebar";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import { getTauriAuthFailure, tauriAutoAuth } from "@/features/auth";
|
||||
import { useTauriBackend } from "@/hooks/use-tauri-backend";
|
||||
import { useTauriUpdate } from "@/hooks/use-tauri-update";
|
||||
import { isTauri } from "@/lib/api-base";
|
||||
import { useRouterState } from "@tanstack/react-router";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { useEffect, useRef, type ReactNode } from "react";
|
||||
import { useEffect, useRef, useState, type ReactNode } from "react";
|
||||
|
||||
interface AppProviderProps {
|
||||
children: ReactNode;
|
||||
|
|
@ -141,6 +142,8 @@ function TauriWrapper({ children }: { children: ReactNode }) {
|
|||
|
||||
const hasResized = useRef(false);
|
||||
const abortRef = useRef(false);
|
||||
const [desktopAuthReady, setDesktopAuthReady] = useState(!isTauri);
|
||||
const [desktopAuthRetry, setDesktopAuthRetry] = useState(0);
|
||||
|
||||
// Show the window once the frontend mounts (for pre-running states)
|
||||
useEffect(() => {
|
||||
|
|
@ -163,20 +166,56 @@ function TauriWrapper({ children }: { children: ReactNode }) {
|
|||
return () => { abortRef.current = true; };
|
||||
}, [status]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTauri) {
|
||||
setDesktopAuthReady(true);
|
||||
return;
|
||||
}
|
||||
if (status !== "running") {
|
||||
setDesktopAuthReady(false);
|
||||
setDesktopAuthRetry(0);
|
||||
return;
|
||||
}
|
||||
|
||||
let disposed = false;
|
||||
setDesktopAuthReady(false);
|
||||
tauriAutoAuth({ force: true }).then((authenticated) => {
|
||||
if (disposed) return;
|
||||
if (authenticated) {
|
||||
setDesktopAuthReady(true);
|
||||
return;
|
||||
}
|
||||
if (!getTauriAuthFailure()) {
|
||||
window.setTimeout(() => {
|
||||
if (!disposed) setDesktopAuthRetry((value) => value + 1);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { disposed = true; };
|
||||
}, [status, desktopAuthRetry]);
|
||||
|
||||
if (!isTauri) return <>{children}</>;
|
||||
|
||||
const content = status === "running" ? (
|
||||
const showApp = status === "running" && desktopAuthReady;
|
||||
const startupStatus = status === "running" ? "starting" : status;
|
||||
const startupProgressDetail =
|
||||
status === "running" && !desktopAuthReady
|
||||
? "Signing in to desktop session..."
|
||||
: progressDetail;
|
||||
|
||||
const content = showApp ? (
|
||||
<>
|
||||
<TauriUpdateLayer isExternalServer={isExternalServer} />
|
||||
{children}
|
||||
</>
|
||||
) : (
|
||||
<StartupScreen
|
||||
status={status}
|
||||
status={startupStatus}
|
||||
logs={logs}
|
||||
error={error}
|
||||
currentStepIndex={currentStepIndex}
|
||||
progressDetail={progressDetail}
|
||||
progressDetail={startupProgressDetail}
|
||||
elevationPackages={elevationPackages}
|
||||
onInstall={startInstall}
|
||||
onRetry={retry}
|
||||
|
|
@ -189,7 +228,7 @@ function TauriWrapper({ children }: { children: ReactNode }) {
|
|||
if (!shouldUseCustomWindowTitlebar()) return content;
|
||||
|
||||
const showSidebarSurface =
|
||||
status === "running" && !HIDDEN_TITLEBAR_SIDEBAR_ROUTES.has(pathname);
|
||||
showApp && !HIDDEN_TITLEBAR_SIDEBAR_ROUTES.has(pathname);
|
||||
|
||||
return (
|
||||
<div className="flex h-dvh min-h-0 flex-col overflow-hidden bg-background [--studio-titlebar-height:34px]">
|
||||
|
|
|
|||
|
|
@ -15,9 +15,13 @@ type DesktopAuthResponse = {
|
|||
refresh_token: string;
|
||||
};
|
||||
|
||||
type TauriAutoAuthOptions = {
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
// Concurrency guard: multiple route guards can call tauriAutoAuth simultaneously.
|
||||
// Without this, the first-launch password-change could race with itself.
|
||||
let pending: Promise<boolean> | null = null;
|
||||
let pending: { promise: Promise<boolean>; force: boolean } | null = null;
|
||||
let lastTauriAuthFailure: string | null = null;
|
||||
|
||||
const TAURI_AUTH_FAILURE_FALLBACK =
|
||||
|
|
@ -49,15 +53,15 @@ function isBackendNotReady(error: unknown): boolean {
|
|||
return authFailureMessage(error).includes(BACKEND_NOT_READY_MESSAGE);
|
||||
}
|
||||
|
||||
async function doTauriAutoAuth(): Promise<boolean> {
|
||||
async function doTauriAutoAuth(options: TauriAutoAuthOptions): Promise<boolean> {
|
||||
// Desktop must handle password-change state internally in Rust.
|
||||
if (hasAuthToken() && !mustChangePassword()) {
|
||||
if (!options.force && hasAuthToken() && !mustChangePassword()) {
|
||||
clearTauriAuthFailure();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try refreshing existing session
|
||||
if (hasRefreshToken()) {
|
||||
if (!options.force && hasRefreshToken()) {
|
||||
const refreshed = await refreshSession();
|
||||
if (refreshed && hasAuthToken() && !mustChangePassword()) {
|
||||
clearTauriAuthFailure();
|
||||
|
|
@ -86,10 +90,17 @@ async function doTauriAutoAuth(): Promise<boolean> {
|
|||
* Returns true if authentication succeeded.
|
||||
* Concurrent calls are coalesced into a single in-flight attempt.
|
||||
*/
|
||||
export function tauriAutoAuth(): Promise<boolean> {
|
||||
export function tauriAutoAuth(
|
||||
options: TauriAutoAuthOptions = {},
|
||||
): Promise<boolean> {
|
||||
if (!isTauri) return Promise.resolve(false);
|
||||
if (!pending) {
|
||||
pending = doTauriAutoAuth().finally(() => { pending = null; });
|
||||
const force = options.force === true;
|
||||
if (!pending || (force && !pending.force)) {
|
||||
let promise: Promise<boolean>;
|
||||
promise = doTauriAutoAuth({ force }).finally(() => {
|
||||
if (pending?.promise === promise) pending = null;
|
||||
});
|
||||
pending = { promise, force };
|
||||
}
|
||||
return pending;
|
||||
return pending.promise;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue