fix and update
This commit is contained in:
@@ -598,7 +598,10 @@ async function trySilentTokenRefresh(): Promise<RefreshSessionResponse | null> {
|
||||
}
|
||||
}
|
||||
return refreshed;
|
||||
} catch {
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 401) {
|
||||
notifyAuthSessionInvalid();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -685,6 +688,8 @@ const API_BURST_MAX_CONCURRENT = 2;
|
||||
const API_BURST_WINDOW_MS = 5000;
|
||||
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';
|
||||
const GATEWAY_CIRCUIT_COOLDOWN_MS = 8_000;
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -694,6 +699,7 @@ let gatewayReadyResolved = false;
|
||||
let gatewayReadyPromise: Promise<void> | null = null;
|
||||
let stableGatewayPromise: Promise<void> | null = null;
|
||||
let gatewayReadyAt = 0;
|
||||
let gatewayCircuitOpenUntil = 0;
|
||||
let activeApiRequests = 0;
|
||||
const apiRequestWaiters: Array<() => void> = [];
|
||||
|
||||
@@ -701,6 +707,7 @@ export function resetApiGatewayWarmup() {
|
||||
gatewayReadyResolved = false;
|
||||
gatewayReadyPromise = null;
|
||||
stableGatewayPromise = null;
|
||||
gatewayCircuitOpenUntil = 0;
|
||||
}
|
||||
|
||||
export function invalidateApiGatewayReady() {
|
||||
@@ -712,6 +719,29 @@ export function invalidateApiGatewayReady() {
|
||||
}
|
||||
}
|
||||
|
||||
function openGatewayCircuit(): void {
|
||||
gatewayReadyResolved = false;
|
||||
gatewayReadyAt = 0;
|
||||
stableGatewayPromise = null;
|
||||
gatewayCircuitOpenUntil = Date.now() + GATEWAY_CIRCUIT_COOLDOWN_MS;
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent(API_NOT_READY_EVENT));
|
||||
}
|
||||
}
|
||||
|
||||
function assertGatewayCircuitClosed(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (Date.now() < gatewayCircuitOpenUntil) {
|
||||
throw new ApiError('Сервер API временно недоступен. Запрос отложен, чтобы не перегружать консоль ошибками.', 503, 'GATEWAY_UNAVAILABLE');
|
||||
}
|
||||
gatewayCircuitOpenUntil = 0;
|
||||
}
|
||||
|
||||
function notifyAuthSessionInvalid(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
window.dispatchEvent(new CustomEvent(AUTH_SESSION_INVALID_EVENT));
|
||||
}
|
||||
|
||||
export function isApiGatewayReady() {
|
||||
return gatewayReadyResolved;
|
||||
}
|
||||
@@ -720,6 +750,7 @@ export function markApiGatewayReady() {
|
||||
if (gatewayReadyResolved) return;
|
||||
gatewayReadyResolved = true;
|
||||
gatewayReadyAt = Date.now();
|
||||
gatewayCircuitOpenUntil = 0;
|
||||
if (typeof window !== 'undefined') {
|
||||
window.dispatchEvent(new CustomEvent(API_READY_EVENT));
|
||||
}
|
||||
@@ -816,6 +847,7 @@ export async function ensureApiGatewayReady(force = false): Promise<void> {
|
||||
gatewayReadyResolved = false;
|
||||
gatewayReadyAt = 0;
|
||||
stableGatewayPromise = null;
|
||||
gatewayCircuitOpenUntil = 0;
|
||||
}
|
||||
|
||||
const probes = force ? 2 : 1;
|
||||
@@ -835,6 +867,7 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
|
||||
let lastNetworkError: unknown;
|
||||
for (let attempt = 0; attempt < GATEWAY_RETRY_ATTEMPTS; attempt++) {
|
||||
try {
|
||||
assertGatewayCircuitClosed();
|
||||
const response = await fetch(url, init);
|
||||
if (response.ok) {
|
||||
markApiGatewayReady();
|
||||
@@ -843,16 +876,24 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
|
||||
await sleep(gatewayRetryDelay(attempt));
|
||||
continue;
|
||||
}
|
||||
if (GATEWAY_RETRY_STATUS.has(response.status)) {
|
||||
openGatewayCircuit();
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
lastNetworkError = error;
|
||||
if (isGatewayUnavailableError(error)) {
|
||||
throw error;
|
||||
}
|
||||
if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
|
||||
await sleep(gatewayRetryDelay(attempt));
|
||||
continue;
|
||||
}
|
||||
openGatewayCircuit();
|
||||
throw mapFetchError(error);
|
||||
}
|
||||
}
|
||||
openGatewayCircuit();
|
||||
throw mapFetchError(lastNetworkError);
|
||||
}
|
||||
|
||||
@@ -871,6 +912,13 @@ export async function apiFetch<T>(path: string, options: RequestInit = {}, token
|
||||
};
|
||||
|
||||
let response: Response;
|
||||
let apiSlotReleased = false;
|
||||
const releaseCurrentApiSlot = () => {
|
||||
if (apiSlotReleased) return;
|
||||
apiSlotReleased = true;
|
||||
releaseApiSlot();
|
||||
};
|
||||
|
||||
try {
|
||||
try {
|
||||
response = await fetchWithGatewayRetry(`${getApiUrl()}${path}`, {
|
||||
@@ -892,10 +940,15 @@ export async function apiFetch<T>(path: string, options: RequestInit = {}, token
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (resolvedToken && error.status === 401 && error.code !== 'TOKEN_EXPIRED') {
|
||||
notifyAuthSessionInvalid();
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (allowRetry && resolvedToken && error.code === 'TOKEN_EXPIRED') {
|
||||
const refreshed = await trySilentTokenRefresh();
|
||||
if (refreshed?.accessToken) {
|
||||
releaseApiSlot();
|
||||
releaseCurrentApiSlot();
|
||||
return apiFetch<T>(path, options, refreshed.accessToken, false);
|
||||
}
|
||||
if (refreshed?.requiresPin) {
|
||||
@@ -903,13 +956,17 @@ export async function apiFetch<T>(path: string, options: RequestInit = {}, token
|
||||
}
|
||||
}
|
||||
|
||||
if (resolvedToken && error.status === 401) {
|
||||
notifyAuthSessionInvalid();
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
markApiGatewayReady();
|
||||
return response.json() as Promise<T>;
|
||||
} finally {
|
||||
releaseApiSlot();
|
||||
releaseCurrentApiSlot();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user