fix and update
This commit is contained in:
@@ -124,13 +124,13 @@ export default function DataPage() {
|
||||
}, [user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!user || !token || isPinLocked) return;
|
||||
if (!isReady || !user || !token || isPinLocked) return;
|
||||
void apiFetch<UserProfileResponse>(`/profile/users/${user.id}`, {}, token)
|
||||
.then((profile) => {
|
||||
if (profile.birthDate) setBirthDate(profile.birthDate);
|
||||
})
|
||||
.catch(() => undefined);
|
||||
}, [isPinLocked, token, user]);
|
||||
}, [isPinLocked, isReady, token, user]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isReady && user && !isPinLocked) void loadDocuments();
|
||||
|
||||
@@ -15,7 +15,7 @@ import { ChatRoom, fetchChatRooms, getApiErrorMessage, setChatRoomPinned } from
|
||||
import { useToast } from '@/components/id/toast-provider';
|
||||
|
||||
export function FamilySidebarPanel() {
|
||||
const { user, token } = useAuth();
|
||||
const { user, token, isContentReady } = useAuth();
|
||||
const {
|
||||
group,
|
||||
groupSummaries,
|
||||
@@ -25,7 +25,7 @@ export function FamilySidebarPanel() {
|
||||
setSelectedGroupId,
|
||||
presenceByUserId,
|
||||
refresh
|
||||
} = useSelectedFamily(Boolean(user && token));
|
||||
} = useSelectedFamily(Boolean(user && token && isContentReady));
|
||||
const { openChatWithMember } = useFamilyOverlay();
|
||||
const { showToast } = useToast();
|
||||
const [inviteOpen, setInviteOpen] = useState(false);
|
||||
|
||||
@@ -86,7 +86,7 @@ function formatMessageTime(value: string) {
|
||||
}
|
||||
|
||||
export function MiniFamilyChat() {
|
||||
const { user, token, isPinLocked } = useAuth();
|
||||
const { user, token, isPinLocked, isContentReady } = useAuth();
|
||||
const { showToast } = useToast();
|
||||
const { subscribe } = useRealtime();
|
||||
const {
|
||||
@@ -96,7 +96,7 @@ export function MiniFamilyChat() {
|
||||
selectedGroupId,
|
||||
setSelectedGroupId,
|
||||
presenceByUserId
|
||||
} = useSelectedFamily(Boolean(user && token && !isPinLocked));
|
||||
} = useSelectedFamily(Boolean(user && token && !isPinLocked && isContentReady));
|
||||
const {
|
||||
miniChatOpen,
|
||||
openMiniChat,
|
||||
|
||||
@@ -105,17 +105,32 @@ function applySessionState(
|
||||
}
|
||||
}
|
||||
|
||||
function readInitialAuthState() {
|
||||
if (typeof window === 'undefined') {
|
||||
return { token: null as string | null, hasStoredSession: false, cachedUser: null as PublicUser | null };
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const { showToast } = useToast();
|
||||
const [token, setToken] = React.useState<string | null>(null);
|
||||
const [user, setUser] = React.useState<PublicUser | null>(null);
|
||||
const initialAuth = React.useMemo(() => readInitialAuthState(), []);
|
||||
const [token, setToken] = React.useState<string | null>(initialAuth.token);
|
||||
const [user, setUser] = React.useState<PublicUser | null>(initialAuth.cachedUser);
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [isApiReady, setIsApiReady] = React.useState(false);
|
||||
const [isSessionReady, setIsSessionReady] = React.useState(false);
|
||||
const [isPinLocked, setIsPinLocked] = React.useState(false);
|
||||
const [hasStoredSession, setHasStoredSession] = React.useState(false);
|
||||
const [hasStoredSession, setHasStoredSession] = React.useState(initialAuth.hasStoredSession);
|
||||
const [lockedSessionId, setLockedSessionId] = React.useState<string | null>(null);
|
||||
const [pinSubmitting, setPinSubmitting] = React.useState(false);
|
||||
const [pinError, setPinError] = React.useState<string | null>(null);
|
||||
@@ -338,6 +353,8 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const message = getApiErrorMessage(error, 'Не удалось загрузить профиль');
|
||||
if (isGatewayUnavailableError(error) && isInitialBootstrap) {
|
||||
setBootstrapError('Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».');
|
||||
setIsApiReady(false);
|
||||
setIsSessionReady(false);
|
||||
return;
|
||||
}
|
||||
if (message) showToast(message);
|
||||
@@ -348,8 +365,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
} finally {
|
||||
if (isInitialBootstrap) {
|
||||
initialBootstrapDoneRef.current = true;
|
||||
setIsApiReady(isApiGatewayReady());
|
||||
setIsSessionReady(isApiGatewayReady());
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { apiFetch, ensureApiGatewayReady } from '@/lib/api';
|
||||
import { apiFetch, ensureApiGatewayReady, isApiGatewayReady, subscribeApiReady } from '@/lib/api';
|
||||
|
||||
interface PublicSettingsContextValue {
|
||||
projectName: string;
|
||||
@@ -39,10 +39,13 @@ export function PublicSettingsProvider({ children }: { children: React.ReactNode
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (isApiGatewayReady()) {
|
||||
void refreshPublicSettings();
|
||||
const onFocus = () => void refreshPublicSettings();
|
||||
window.addEventListener('focus', onFocus);
|
||||
return () => window.removeEventListener('focus', onFocus);
|
||||
return;
|
||||
}
|
||||
return subscribeApiReady(() => {
|
||||
void refreshPublicSettings();
|
||||
});
|
||||
}, [refreshPublicSettings]);
|
||||
|
||||
const value = useMemo(
|
||||
|
||||
@@ -682,7 +682,8 @@ const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
|
||||
const GATEWAY_RETRY_ATTEMPTS = 4;
|
||||
const GATEWAY_PROBE_MAX_ATTEMPTS = 48;
|
||||
const GATEWAY_PROBE_BASE_DELAY_MS = 450;
|
||||
const GATEWAY_STABLE_PROBES = 3;
|
||||
const GATEWAY_STABLE_PROBE_INTERVAL_MS = 800;
|
||||
const GATEWAY_STABLE_MAX_ROUNDS = 18;
|
||||
const API_MAX_CONCURRENT = 6;
|
||||
const API_BURST_MAX_CONCURRENT = 2;
|
||||
const API_BURST_WINDOW_MS = 5000;
|
||||
@@ -742,24 +743,24 @@ export function subscribeApiNotReady(listener: () => void): () => void {
|
||||
}
|
||||
|
||||
/** Несколько подряд успешных /health через nginx — защита от «один OK, потом 502». */
|
||||
export async function waitForStableGateway(stableProbes = GATEWAY_STABLE_PROBES): Promise<void> {
|
||||
export async function waitForStableGateway(stableProbes = 3): Promise<void> {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
gatewayReadyResolved = false;
|
||||
gatewayReadyAt = 0;
|
||||
|
||||
let streak = 0;
|
||||
for (let attempt = 0; attempt < 24 && streak < stableProbes; attempt += 1) {
|
||||
for (let attempt = 0; attempt < GATEWAY_STABLE_MAX_ROUNDS && streak < stableProbes; attempt += 1) {
|
||||
if (await probeGatewayAlive()) {
|
||||
streak += 1;
|
||||
if (streak >= stableProbes) {
|
||||
markApiGatewayReady();
|
||||
return;
|
||||
}
|
||||
await sleep(500);
|
||||
await sleep(GATEWAY_STABLE_PROBE_INTERVAL_MS);
|
||||
} else {
|
||||
streak = 0;
|
||||
await sleep(Math.min(700 + attempt * 250, 2500));
|
||||
await sleep(Math.min(900 + attempt * 300, 2800));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="2.4.1"
|
||||
SCRIPT_VERSION="2.4.2"
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
@@ -2035,13 +2035,14 @@ nginx_proxy_headers=" proxy_set_header Host \$host;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
proxy_set_header X-Forwarded-Host \$host;
|
||||
proxy_set_header Authorization \$http_authorization;
|
||||
proxy_set_header Connection \"close\";
|
||||
proxy_read_timeout 300s;
|
||||
proxy_connect_timeout 75s;"
|
||||
|
||||
# Встроенный DNS Docker. valid=2s — быстрее подхватывает новый IP после recreate api-gateway.
|
||||
NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=2s;
|
||||
# Встроенный DNS Docker. valid=1s — быстрее подхватывает новый IP после recreate api-gateway.
|
||||
NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=1s;
|
||||
resolver_timeout 3s;"
|
||||
NGINX_DOCKER_SERVER_DNS=" resolver 127.0.0.11 ipv6=off valid=2s;
|
||||
NGINX_DOCKER_SERVER_DNS=" resolver 127.0.0.11 ipv6=off valid=1s;
|
||||
resolver_timeout 3s;"
|
||||
|
||||
# Имя nginx-переменной из ключа апстрима (только [A-Za-z0-9_]).
|
||||
|
||||
Reference in New Issue
Block a user