fix and update

This commit is contained in:
lendry
2026-06-30 16:03:33 +03:00
parent 69063c8fba
commit 2eeb928a72
6 changed files with 138 additions and 19 deletions

View File

@@ -107,15 +107,13 @@ function applySessionState(
function readInitialAuthState() {
if (typeof window === 'undefined') {
return { token: null as string | null, hasStoredSession: false, cachedUser: null as PublicUser | null };
return { token: null as string | null, hasStoredSession: false };
}
const token = window.localStorage.getItem(AUTH_TOKEN_KEY);
const refresh = window.localStorage.getItem(AUTH_REFRESH_KEY);
const cachedUser = readCachedUserProfile();
return {
token,
hasStoredSession: Boolean(refresh || token),
cachedUser
hasStoredSession: Boolean(refresh || token)
};
}
@@ -125,7 +123,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const { showToast } = useToast();
const initialAuth = React.useMemo(() => readInitialAuthState(), []);
const [token, setToken] = React.useState<string | null>(initialAuth.token);
const [user, setUser] = React.useState<PublicUser | null>(initialAuth.cachedUser);
const [user, setUser] = React.useState<PublicUser | null>(null);
const [isLoading, setIsLoading] = React.useState(true);
const [isApiReady, setIsApiReady] = React.useState(false);
const [isSessionReady, setIsSessionReady] = React.useState(false);
@@ -269,8 +267,12 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setHasStoredSession(Boolean(refreshToken));
if (!currentToken && !refreshToken) {
window.localStorage.removeItem(AUTH_USER_CACHE_KEY);
setUser(null);
setToken(null);
initialBootstrapDoneRef.current = true;
setIsSessionReady(true);
setIsApiReady(true);
setIsLoading(false);
return;
}
@@ -282,7 +284,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
}
try {
for (let bootstrapAttempt = 0; bootstrapAttempt < 6; bootstrapAttempt += 1) {
for (let bootstrapAttempt = 0; bootstrapAttempt < 4; bootstrapAttempt += 1) {
try {
if (isInitialBootstrap) {
await waitForStableGateway(3);
@@ -293,11 +295,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
try {
const session = await fetchAuthSession(currentToken);
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
setIsApiReady(true);
setIsSessionReady(true);
return;
} catch (error) {
if (isPinRequiredError(error)) {
activatePinLock(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
setIsApiReady(true);
setIsSessionReady(true);
return;
}
if (!(error instanceof ApiError) || error.code !== 'TOKEN_EXPIRED') {
@@ -321,11 +326,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cacheUserProfile(refreshed.user);
}
activatePinLock(refreshed.sessionId ?? '');
setIsApiReady(true);
setIsSessionReady(true);
return;
}
if (refreshed.accessToken) {
const session = await fetchAuthSession(refreshed.accessToken);
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
setIsApiReady(true);
setIsSessionReady(true);
return;
}
@@ -333,17 +341,26 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
throw new ApiError('Сессия недействительна', 401);
} catch (error) {
if (isGatewayUnavailableError(error) && isInitialBootstrap && bootstrapAttempt + 1 < 6) {
if (isGatewayUnavailableError(error) && isInitialBootstrap && bootstrapAttempt + 1 < 4) {
invalidateApiGatewayReady();
if (bootstrapAttempt >= 1) {
setBootstrapError(
'Не удалось подключиться к серверу API (502). Подождите несколько секунд и нажмите «Повторить».'
);
}
await new Promise((resolve) => setTimeout(resolve, 1200));
continue;
}
if (isPinRequiredError(error)) {
activatePinLock(error.sessionId ?? window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
setIsApiReady(true);
setIsSessionReady(true);
return;
}
if (refreshToken && error instanceof ApiError && error.status === 403) {
activatePinLock(window.localStorage.getItem(AUTH_SESSION_KEY) ?? '');
setIsApiReady(true);
setIsSessionReady(true);
return;
}
if (error instanceof ApiError && (error.code === 'NO_REFRESH' || error.status === 401)) {
@@ -612,7 +629,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const isBootstrapping =
!isPinLocked &&
Boolean(hasStoredSession || token || user) &&
Boolean(hasStoredSession || token) &&
(isLoading || !isApiReady || !isSessionReady);
return (
@@ -649,6 +666,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
? () => {
setBootstrapError(null);
initialBootstrapDoneRef.current = false;
invalidateApiGatewayReady();
void refreshProfile();
}
: undefined

View File

@@ -7,14 +7,17 @@ import { useAuth } from '@/components/id/auth-provider';
export function useRequireAuth() {
const router = useRouter();
const pathname = usePathname();
const { user, isLoading, isApiReady, isSessionReady, isPinLocked, hasStoredSession } = useAuth();
const { user, isLoading, isApiReady, isSessionReady, isPinLocked, hasStoredSession, token } = useAuth();
const authResolved = !isLoading && isSessionReady;
const isAuthenticated = Boolean(user || isPinLocked);
useEffect(() => {
if (isLoading || !isApiReady || !isSessionReady) return;
if (user || isPinLocked || hasStoredSession) return;
if (!authResolved) return;
if (isAuthenticated || hasStoredSession || token) return;
if (pathname.startsWith('/auth/')) return;
router.replace('/auth/login');
}, [hasStoredSession, isApiReady, isLoading, isPinLocked, isSessionReady, pathname, router, user]);
}, [authResolved, hasStoredSession, isAuthenticated, pathname, router, token]);
return {
user,
@@ -22,6 +25,7 @@ export function useRequireAuth() {
isPinLocked,
isApiReady,
isSessionReady,
isReady: !isLoading && isApiReady && isSessionReady && Boolean(user || isPinLocked || hasStoredSession)
authResolved,
isReady: authResolved && isAuthenticated && isApiReady
};
}

View File

@@ -449,7 +449,10 @@ export function isPinRequiredError(error: unknown): error is ApiError {
}
export function isGatewayUnavailableError(error: unknown): boolean {
return error instanceof ApiError && [502, 503, 504].includes(error.status);
return (
error instanceof ApiError &&
(error.code === 'GATEWAY_UNAVAILABLE' || [502, 503, 504].includes(error.status))
);
}
export function getApiErrorMessage(error: unknown, fallback: string): string | null {
@@ -681,7 +684,7 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
const GATEWAY_RETRY_ATTEMPTS = 4;
const GATEWAY_STABLE_PROBE_INTERVAL_MS = 800;
const GATEWAY_STABLE_MAX_ROUNDS = 18;
const GATEWAY_STABLE_MAX_ROUNDS = 12;
const API_MAX_CONCURRENT = 6;
const API_BURST_MAX_CONCURRENT = 2;
const API_BURST_WINDOW_MS = 5000;