fix and update
This commit is contained in:
@@ -479,7 +479,7 @@ function notifyPinRequired(sessionId: string) {
|
||||
}
|
||||
|
||||
export async function fetchAuthSession(token?: string | null): Promise<AuthSessionResponse> {
|
||||
return apiFetch<AuthSessionResponse>('/auth/session', {}, token);
|
||||
return apiFetch<AuthSessionResponse>('/auth/session', {}, token, true, { critical: true });
|
||||
}
|
||||
|
||||
export async function approveOAuthAuthorization(params: URLSearchParams, token: string) {
|
||||
@@ -688,7 +688,7 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
|
||||
}
|
||||
|
||||
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
|
||||
const GATEWAY_RETRY_ATTEMPTS = 2;
|
||||
const GATEWAY_RETRY_ATTEMPTS = 3;
|
||||
const GATEWAY_CRITICAL_RETRY_ATTEMPTS = 4;
|
||||
|
||||
export interface ApiFetchBehavior {
|
||||
@@ -700,8 +700,8 @@ export interface ApiFetchBehavior {
|
||||
const GATEWAY_STABLE_PROBE_INTERVAL_MS = 800;
|
||||
const GATEWAY_STABLE_MAX_ROUNDS = 8;
|
||||
const API_MAX_CONCURRENT = 6;
|
||||
const API_BURST_MAX_CONCURRENT = 2;
|
||||
const API_BURST_WINDOW_MS = 5000;
|
||||
const API_BURST_MAX_CONCURRENT = 4;
|
||||
const API_BURST_WINDOW_MS = 8000;
|
||||
const API_READY_EVENT = 'idp-api-ready';
|
||||
const API_NOT_READY_EVENT = 'idp-api-not-ready';
|
||||
export const AUTH_SESSION_INVALID_EVENT = 'idp-auth-session-invalid';
|
||||
@@ -746,6 +746,8 @@ function shouldBypassGatewayCircuit(behavior?: ApiFetchBehavior): boolean {
|
||||
|
||||
function openGatewayCircuit(behavior?: ApiFetchBehavior): void {
|
||||
if (shouldBypassGatewayCircuit(behavior)) return;
|
||||
// До стабильного прогрева /health circuit не открываем — иначе каждая перезагрузка ловит ложный 502.
|
||||
if (!gatewayReadyResolved) return;
|
||||
gatewayReadyResolved = false;
|
||||
gatewayReadyAt = 0;
|
||||
stableGatewayPromise = null;
|
||||
@@ -757,6 +759,7 @@ function openGatewayCircuit(behavior?: ApiFetchBehavior): void {
|
||||
|
||||
function assertGatewayCircuitClosed(behavior?: ApiFetchBehavior): void {
|
||||
if (typeof window === 'undefined' || shouldBypassGatewayCircuit(behavior)) return;
|
||||
if (!gatewayReadyAt && !gatewayCircuitOpenUntil) return;
|
||||
if (Date.now() < gatewayCircuitOpenUntil) {
|
||||
throw new ApiError('Сервер API временно недоступен. Запрос отложен, чтобы не перегружать консоль ошибками.', 503, 'GATEWAY_UNAVAILABLE');
|
||||
}
|
||||
@@ -876,7 +879,7 @@ export async function ensureApiGatewayReady(force = false): Promise<void> {
|
||||
gatewayCircuitOpenUntil = 0;
|
||||
}
|
||||
|
||||
const probes = force ? 2 : 1;
|
||||
const probes = force ? 2 : 2;
|
||||
gatewayReadyPromise = waitForStableGateway(probes).finally(() => {
|
||||
gatewayReadyPromise = null;
|
||||
});
|
||||
@@ -900,9 +903,6 @@ async function fetchWithGatewayRetry(
|
||||
try {
|
||||
assertGatewayCircuitClosed(behavior);
|
||||
const response = await fetch(url, init);
|
||||
if (response.ok) {
|
||||
markApiGatewayReady();
|
||||
}
|
||||
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < maxAttempts) {
|
||||
await sleep(gatewayRetryDelay(attempt));
|
||||
continue;
|
||||
@@ -935,6 +935,14 @@ export async function apiFetch<T>(
|
||||
allowRetry = true,
|
||||
behavior: ApiFetchBehavior = {}
|
||||
): Promise<T> {
|
||||
if (!gatewayReadyResolved && !behavior.silent) {
|
||||
try {
|
||||
await ensureApiGatewayReady();
|
||||
} catch {
|
||||
// fetchWithGatewayRetry попробует сам после кратковременного 502
|
||||
}
|
||||
}
|
||||
|
||||
await acquireApiSlot();
|
||||
|
||||
const resolvedToken = resolveAccessToken(token);
|
||||
@@ -1000,7 +1008,6 @@ export async function apiFetch<T>(
|
||||
throw error;
|
||||
}
|
||||
|
||||
markApiGatewayReady();
|
||||
return response.json() as Promise<T>;
|
||||
} finally {
|
||||
releaseCurrentApiSlot();
|
||||
@@ -1263,11 +1270,11 @@ export async function fetchFamilyGroups(userId: string, token?: string | null) {
|
||||
if (!accessToken) {
|
||||
throw new ApiError('Не авторизован', 401, 'NO_TOKEN');
|
||||
}
|
||||
return apiFetch<{ groups?: FamilyGroup[] }>(`/family/users/${userId}/groups`, {}, accessToken);
|
||||
return apiFetch<{ groups?: FamilyGroup[] }>(`/family/users/${userId}/groups`, {}, accessToken, true, { silent: true });
|
||||
}
|
||||
|
||||
export async function fetchFamilyGroup(groupId: string, token?: string | null) {
|
||||
return apiFetch<FamilyGroup>(`/family/groups/${groupId}`, {}, token);
|
||||
return apiFetch<FamilyGroup>(`/family/groups/${groupId}`, {}, token, true, { silent: true });
|
||||
}
|
||||
|
||||
export async function updateFamilyGroup(groupId: string, name: string, token?: string | null) {
|
||||
@@ -1325,7 +1332,7 @@ export async function fetchNotifications(token?: string | null, unreadOnly = fal
|
||||
}
|
||||
|
||||
export async function fetchUnreadNotificationCount(token?: string | null) {
|
||||
return apiFetch<{ count: number }>('/notifications/unread-count', {}, token);
|
||||
return apiFetch<{ count: number }>('/notifications/unread-count', {}, token, true, { silent: true });
|
||||
}
|
||||
|
||||
export async function markNotificationRead(notificationId: string, token?: string | null) {
|
||||
@@ -1584,7 +1591,7 @@ export async function fetchFamilyPresence(groupId: string, token?: string | null
|
||||
if (!accessToken) {
|
||||
throw new ApiError('Не авторизован', 401, 'NO_TOKEN');
|
||||
}
|
||||
return apiFetch<FamilyPresence>(`/family/groups/${groupId}/presence`, {}, accessToken);
|
||||
return apiFetch<FamilyPresence>(`/family/groups/${groupId}/presence`, {}, accessToken, true, { silent: true });
|
||||
}
|
||||
|
||||
/** @deprecated Используйте getWsUrl() — URL может переключаться на same-origin /idp-api/ws в браузере */
|
||||
|
||||
Reference in New Issue
Block a user