Fix desktop auth gate after backend startup

This commit is contained in:
wasimysaid 2026-04-27 05:40:49 +02:00
commit c23e91e009
2 changed files with 63 additions and 13 deletions

View file

@ -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]">

View file

@ -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;
}