fix and update

This commit is contained in:
lendry
2026-06-30 11:38:41 +03:00
parent df4bbba133
commit 46adf60ab8
5 changed files with 103 additions and 19 deletions

View File

@@ -1,15 +1,38 @@
'use client';
import { Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
export function AppBootstrapScreen() {
export function AppBootstrapScreen({
error,
onRetry
}: {
error?: string | null;
onRetry?: () => void;
}) {
return (
<div className="fixed inset-0 z-[120] flex min-h-screen flex-col items-center justify-center bg-white px-6">
<Loader2 className="h-10 w-10 animate-spin text-[#3390ec]" aria-hidden />
<p className="mt-5 text-base font-medium text-[#101828]">Подключение к серверу</p>
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
Подготавливаем ваш профиль и сервисы. Это займёт несколько секунд.
</p>
{error ? (
<>
<p className="max-w-md text-center text-base font-medium text-[#101828]">{error}</p>
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
Сервер авторизации временно недоступен. Обычно помогает повтор через несколько секунд.
</p>
{onRetry ? (
<Button className="mt-6 rounded-xl" onClick={onRetry}>
Повторить подключение
</Button>
) : null}
</>
) : (
<>
<Loader2 className="h-10 w-10 animate-spin text-[#3390ec]" aria-hidden />
<p className="mt-5 text-base font-medium text-[#101828]">Подключение к серверу</p>
<p className="mt-2 max-w-sm text-center text-sm text-[#667085]">
Подготавливаем ваш профиль и сервисы. Это займёт несколько секунд.
</p>
</>
)}
</div>
);
}

View File

@@ -102,7 +102,6 @@ function applySessionState(
setters.activatePinLock(session.sessionId ?? '');
} else {
setters.clearPinLock();
scheduleFedcmSessionSync();
}
}
@@ -120,6 +119,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
const [pinSubmitting, setPinSubmitting] = React.useState(false);
const [pinError, setPinError] = React.useState<string | null>(null);
const [bootstrapError, setBootstrapError] = React.useState<string | null>(null);
const isPinLockedRef = React.useRef(false);
const refreshInFlightRef = React.useRef<Promise<void> | null>(null);
const initialBootstrapDoneRef = React.useRef(false);
@@ -182,6 +182,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
async (accessToken: string) => {
setIsSessionReady(false);
setIsLoading(true);
setBootstrapError(null);
invalidateApiGatewayReady();
setIsApiReady(false);
@@ -192,6 +193,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
initialBootstrapDoneRef.current = true;
setIsApiReady(true);
setIsSessionReady(true);
} catch (error) {
setBootstrapError(
isGatewayUnavailableError(error)
? 'Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».'
: (getApiErrorMessage(error, 'Не удалось подключиться к серверу API') ?? 'Ошибка подключения')
);
throw error;
} finally {
setIsLoading(false);
}
@@ -213,7 +221,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
await warmUpAndApplySession(auth.accessToken);
if (auth.pinVerified !== false) {
scheduleFedcmSessionSync(auth.accessToken, 2500);
scheduleFedcmSessionSync(auth.accessToken, 8000);
}
},
[clearPinLock, warmUpAndApplySession]
@@ -255,6 +263,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const isInitialBootstrap = !initialBootstrapDoneRef.current;
if (isInitialBootstrap) {
setIsLoading(true);
setBootstrapError(null);
}
try {
@@ -327,6 +336,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
return;
}
const message = getApiErrorMessage(error, 'Не удалось загрузить профиль');
if (isGatewayUnavailableError(error) && isInitialBootstrap) {
setBootstrapError('Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».');
return;
}
if (message) showToast(message);
logout();
return;
@@ -613,7 +626,22 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
logout
}}
>
{isBootstrapping ? <AppBootstrapScreen /> : children}
{isBootstrapping ? (
<AppBootstrapScreen
error={bootstrapError}
onRetry={
bootstrapError
? () => {
setBootstrapError(null);
initialBootstrapDoneRef.current = false;
void refreshProfile();
}
: undefined
}
/>
) : (
children
)}
<PinLockModal open={isPinLocked} isSubmitting={pinSubmitting} error={pinError} onSubmit={handlePinUnlock} />
</AuthContext.Provider>
);

View File

@@ -565,9 +565,11 @@ let fedcmSyncTimer: ReturnType<typeof setTimeout> | null = null;
let fedcmSyncInFlight = false;
/** Откладывает FedCM sync после OAuth/bootstrap, чтобы не конкурировать с consent/session. */
export function scheduleFedcmSessionSync(accessToken?: string | null, delayMs = 1500) {
export function scheduleFedcmSessionSync(accessToken?: string | null, delayMs = 8000) {
if (typeof window === 'undefined') return;
if (window.location.pathname.startsWith('/auth/oauth/authorize')) return;
if (window.location.pathname.startsWith('/auth/login')) return;
if (window.location.pathname.startsWith('/auth/register')) return;
if (fedcmSyncTimer) {
clearTimeout(fedcmSyncTimer);
}
@@ -747,17 +749,17 @@ export async function waitForStableGateway(stableProbes = GATEWAY_STABLE_PROBES)
gatewayReadyAt = 0;
let streak = 0;
for (let attempt = 0; attempt < 40 && streak < stableProbes; attempt += 1) {
for (let attempt = 0; attempt < 24 && streak < stableProbes; attempt += 1) {
if (await probeGatewayAlive()) {
streak += 1;
if (streak >= stableProbes) {
markApiGatewayReady();
return;
}
await sleep(400);
await sleep(500);
} else {
streak = 0;
await sleep(Math.min(500 + attempt * 220, 2200));
await sleep(Math.min(700 + attempt * 250, 2500));
}
}