diff --git a/apps/frontend/components/id/auth-provider.tsx b/apps/frontend/components/id/auth-provider.tsx index 357194f..4ce2a4d 100644 --- a/apps/frontend/components/id/auth-provider.tsx +++ b/apps/frontend/components/id/auth-provider.tsx @@ -160,7 +160,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setHasStoredSession(true); clearPinLock(); if (auth.pinVerified !== false) { - void syncFedcmSession(auth.accessToken); + window.setTimeout(() => { + void syncFedcmSession(auth.accessToken); + }, 300); } }, [clearPinLock] diff --git a/apps/frontend/lib/api.ts b/apps/frontend/lib/api.ts index ec2c2a3..1aba90f 100644 --- a/apps/frontend/lib/api.ts +++ b/apps/frontend/lib/api.ts @@ -506,7 +506,7 @@ export async function refreshAuthSession(): Promise { throw new ApiError('Refresh token отсутствует', 401, 'NO_REFRESH'); } - const response = await fetch(`${getApiUrl()}/auth/refresh`, { + const response = await fetchWithGatewayRetry(`${getApiUrl()}/auth/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken, sessionId: sessionId ?? undefined }) @@ -522,7 +522,7 @@ export async function refreshAuthSession(): Promise { export async function syncFedcmSession(accessToken: string) { if (typeof window === 'undefined' || !accessToken.trim()) return; try { - await fetch(`${getApiUrl()}/fedcm/session/sync`, { + await fetchWithGatewayRetry(`${getApiUrl()}/fedcm/session/sync`, { method: 'POST', headers: { Authorization: `Bearer ${accessToken.trim()}` }, credentials: 'include' @@ -624,6 +624,36 @@ export function buildSystemSettingPayload(setting: Pick { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +/** Повтор при кратковременном 502/503/504 (nginx не достучался до api-gateway после перезапуска). */ +async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise { + let lastNetworkError: unknown; + for (let attempt = 0; attempt < GATEWAY_RETRY_ATTEMPTS; attempt++) { + try { + const response = await fetch(url, init); + if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < GATEWAY_RETRY_ATTEMPTS) { + await sleep(250 * (attempt + 1)); + continue; + } + return response; + } catch (error) { + lastNetworkError = error; + if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) { + await sleep(250 * (attempt + 1)); + continue; + } + throw mapFetchError(error); + } + } + throw mapFetchError(lastNetworkError); +} + export async function apiFetch(path: string, options: RequestInit = {}, token?: string | null, allowRetry = true): Promise { const resolvedToken = token ?? (typeof window !== 'undefined' ? window.localStorage.getItem(AUTH_TOKEN_KEY) : null); const method = (options.method ?? 'GET').toUpperCase(); @@ -638,11 +668,14 @@ export async function apiFetch(path: string, options: RequestInit = {}, token let response: Response; try { - response = await fetch(`${getApiUrl()}${path}`, { + response = await fetchWithGatewayRetry(`${getApiUrl()}${path}`, { ...options, headers }); } catch (error) { + if (error instanceof ApiError) { + throw error; + } throw mapFetchError(error); } diff --git a/install.sh b/install.sh index 0429e72..4d6abd0 100644 --- a/install.sh +++ b/install.sh @@ -10,7 +10,7 @@ set -euo pipefail -SCRIPT_VERSION="2.3.6" +SCRIPT_VERSION="2.3.7" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" @@ -1912,16 +1912,16 @@ render_proxy_rewrite() { fi } -# proxy_pass для /idp-api/ — срезает префикс /idp-api/ и проксирует на корень. +# proxy_pass для /idp-api/ — named capture надёжнее rewrite+break с переменной upstream. render_proxy_idp_api() { local key="api" up var up="$(nginx_upstream "$key")" if [[ "$NGINX_MODE" == "docker" ]]; then var="$(nginx_upstream_var "$key")" - printf '%s\n set $%s %s;\n rewrite ^/idp-api/(.*)$ /$1 break;\n proxy_pass $%s;' \ + printf '%s\n set $%s %s;\n proxy_pass $%s/$api_path$is_args$args;' \ "$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var" else - printf ' proxy_pass %s/;' "$up" + printf ' proxy_pass %s/$api_path$is_args$args;' "$up" fi } @@ -2095,9 +2095,10 @@ EOF } build_nginx_idp_api_proxy_block() { - local proxy_ws proxy_idp proxy_oauth proxy_wk proxy_fedcm + local proxy_ws proxy_idp proxy_auth proxy_oauth proxy_wk proxy_fedcm proxy_ws="$(render_proxy_rewrite ws /ws)" proxy_idp="$(render_proxy_idp_api)" + proxy_auth="$(render_proxy_root api)" proxy_oauth="$(render_proxy_root api)" proxy_wk="$(render_proxy_root api)" proxy_fedcm="$(render_proxy_root api)" @@ -2114,12 +2115,18 @@ ${proxy_ws} proxy_read_timeout 86400s; } - location /idp-api/ { + location ~ ^/idp-api/(?.*)$ { ${proxy_idp} proxy_http_version 1.1; ${nginx_proxy_headers} } + location /auth/ { +${proxy_auth} + proxy_http_version 1.1; +${nginx_proxy_headers} + } + location /oauth/ { ${proxy_oauth} proxy_http_version 1.1;