From 4cd75cb0b1557327e6e0726c620ace2194c4397d Mon Sep 17 00:00:00 2001 From: lendry Date: Mon, 29 Jun 2026 21:36:32 +0300 Subject: [PATCH] fix and update --- .../components/family/family-group-view.tsx | 14 ++++----- .../components/id/app-bootstrap-screen.tsx | 15 +++++++++ apps/frontend/components/id/auth-provider.tsx | 31 ++++++++++++++++--- apps/frontend/lib/api.ts | 23 +++++++++----- 4 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 apps/frontend/components/id/app-bootstrap-screen.tsx diff --git a/apps/frontend/components/family/family-group-view.tsx b/apps/frontend/components/family/family-group-view.tsx index 921b2df..311d22f 100644 --- a/apps/frontend/components/family/family-group-view.tsx +++ b/apps/frontend/components/family/family-group-view.tsx @@ -1699,7 +1699,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) { > {activeRoom ? ( <> -
+
) : null} diff --git a/apps/frontend/components/id/app-bootstrap-screen.tsx b/apps/frontend/components/id/app-bootstrap-screen.tsx new file mode 100644 index 0000000..eb39153 --- /dev/null +++ b/apps/frontend/components/id/app-bootstrap-screen.tsx @@ -0,0 +1,15 @@ +'use client'; + +import { Loader2 } from 'lucide-react'; + +export function AppBootstrapScreen() { + return ( +
+ +

Подключение к серверу

+

+ Подготавливаем ваш профиль и сервисы. Это займёт несколько секунд. +

+
+ ); +} diff --git a/apps/frontend/components/id/auth-provider.tsx b/apps/frontend/components/id/auth-provider.tsx index 3a560ab..be1029b 100644 --- a/apps/frontend/components/id/auth-provider.tsx +++ b/apps/frontend/components/id/auth-provider.tsx @@ -22,12 +22,14 @@ import { PinVerificationResponse, PublicUser, refreshAuthSession, + resetApiGatewayWarmup, resetPinRequiredNotification, setPinRequiredHandler, syncFedcmSession } from '@/lib/api'; import { useToast } from './toast-provider'; import { PinLockModal } from './pin-lock-modal'; +import { AppBootstrapScreen } from './app-bootstrap-screen'; import { EMBEDDED_AUTH_MESSAGE, type EmbeddedAuthPayload } from '@/lib/embedded-auth-bridge'; interface AuthContextValue { @@ -112,6 +114,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const [pinError, setPinError] = React.useState(null); const isPinLockedRef = React.useRef(false); const refreshInFlightRef = React.useRef | null>(null); + const initialBootstrapDoneRef = React.useRef(false); React.useEffect(() => { isPinLockedRef.current = isPinLocked; @@ -160,8 +163,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { cacheUserProfile(auth.user); setHasStoredSession(true); clearPinLock(); + initialBootstrapDoneRef.current = false; + resetApiGatewayWarmup(); setIsLoading(true); - void ensureApiGatewayReady().finally(() => setIsLoading(false)); + void ensureApiGatewayReady().finally(() => { + initialBootstrapDoneRef.current = true; + setIsLoading(false); + }); if (auth.pinVerified !== false) { void syncFedcmSession(auth.accessToken); } @@ -174,6 +182,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { window.localStorage.removeItem(AUTH_REFRESH_KEY); window.localStorage.removeItem(AUTH_SESSION_KEY); window.localStorage.removeItem(AUTH_USER_CACHE_KEY); + resetApiGatewayWarmup(); + initialBootstrapDoneRef.current = false; setToken(null); setUser(null); setHasStoredSession(false); @@ -192,10 +202,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setHasStoredSession(Boolean(refreshToken)); if (!currentToken && !refreshToken) { + initialBootstrapDoneRef.current = true; setIsLoading(false); return; } + const isInitialBootstrap = !initialBootstrapDoneRef.current; + if (isInitialBootstrap) { + setIsLoading(true); + resetApiGatewayWarmup(); + } + try { if (currentToken) { setToken(currentToken); @@ -259,14 +276,17 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const hasSession = Boolean( window.localStorage.getItem(AUTH_TOKEN_KEY) || window.localStorage.getItem(AUTH_REFRESH_KEY) ); - if (hasSession) { + if (hasSession && isInitialBootstrap) { try { await ensureApiGatewayReady(); } catch { // Продолжаем даже если gateway не ответил в срок прогрева. } } - setIsLoading(false); + if (isInitialBootstrap) { + initialBootstrapDoneRef.current = true; + setIsLoading(false); + } refreshInFlightRef.current = null; } })(); @@ -519,6 +539,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { [login] ); + const isBootstrapping = + isLoading && !isPinLocked && Boolean(hasStoredSession || token || user); + return ( - {children} + {isBootstrapping ? : children} ); diff --git a/apps/frontend/lib/api.ts b/apps/frontend/lib/api.ts index 7d9a709..7ffa9c8 100644 --- a/apps/frontend/lib/api.ts +++ b/apps/frontend/lib/api.ts @@ -505,6 +505,8 @@ export async function refreshAuthSession(): Promise { throw new ApiError('Refresh token недоступен на сервере', 401, 'NO_REFRESH'); } + await ensureApiGatewayReady(); + const refreshToken = window.localStorage.getItem(AUTH_REFRESH_KEY); const sessionId = window.localStorage.getItem(AUTH_SESSION_KEY); if (!refreshToken) { @@ -632,8 +634,9 @@ export function buildSystemSettingPayload(setting: Pick { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -651,9 +654,9 @@ export function markApiGatewayReady() { gatewayReadyResolved = true; } -async function probeGatewayHealth(): Promise { +async function probeGatewayReady(): Promise { try { - const response = await fetch(`${getApiUrl()}/health`, { + const response = await fetch(`${getApiUrl()}/health/ready`, { method: 'GET', cache: 'no-store' }); @@ -663,17 +666,23 @@ async function probeGatewayHealth(): Promise { } } -/** Ждёт готовности api-gateway (кратковременные 502 после перезапуска nginx/upstream). */ +/** Ждёт готовности api-gateway и sso-core (readiness по gRPC, не liveness /health). */ export async function ensureApiGatewayReady(force = false): Promise { if (typeof window === 'undefined') return; if (gatewayReadyResolved && !force) return; if (gatewayReadyPromise && !force) return gatewayReadyPromise; gatewayReadyPromise = (async () => { + let consecutiveOk = 0; for (let attempt = 0; attempt < GATEWAY_WARMUP_MAX_ATTEMPTS; attempt++) { - if (await probeGatewayHealth()) { - gatewayReadyResolved = true; - return; + if (await probeGatewayReady()) { + consecutiveOk += 1; + if (consecutiveOk >= GATEWAY_READY_CONSECUTIVE_OK) { + gatewayReadyResolved = true; + return; + } + } else { + consecutiveOk = 0; } await sleep(GATEWAY_WARMUP_DELAY_MS); }