fix and update

This commit is contained in:
lendry
2026-06-30 17:57:09 +03:00
parent 521de7ea00
commit 2ea790d21d
5 changed files with 68 additions and 46 deletions

View File

@@ -29,7 +29,6 @@ import {
setPinRequiredHandler,
subscribeApiReady,
subscribeApiNotReady,
waitForStableGateway,
} from '@/lib/api';
import { useToast } from './toast-provider';
import { PinLockModal } from './pin-lock-modal';
@@ -200,7 +199,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsApiReady(false);
try {
await waitForStableGateway(1);
const session = await fetchAuthSession(accessToken);
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
initialBootstrapDoneRef.current = true;
@@ -230,14 +228,32 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
cacheUserProfile(auth.user);
setHasStoredSession(true);
clearPinLock();
setBootstrapError(null);
initialBootstrapDoneRef.current = true;
setIsApiReady(true);
setIsSessionReady(true);
setIsLoading(false);
await warmUpAndApplySession(auth.accessToken);
void fetchAuthSession(auth.accessToken)
.then((session) => {
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
})
.catch((error) => {
if (isPinRequiredError(error)) {
activatePinLock(error.sessionId ?? auth.sessionId);
return;
}
if (!isGatewayUnavailableError(error)) {
const message = getApiErrorMessage(error, 'Не удалось обновить профиль после входа');
if (message) showToast(message);
}
});
if (auth.pinVerified !== false) {
scheduleFedcmSessionSync(auth.accessToken, 8000);
}
},
[clearPinLock, warmUpAndApplySession]
[activatePinLock, clearPinLock, showToast]
);
const logout = React.useCallback(() => {
@@ -286,10 +302,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
try {
for (let bootstrapAttempt = 0; bootstrapAttempt < 4; bootstrapAttempt += 1) {
try {
if (isInitialBootstrap) {
await waitForStableGateway(1);
}
if (currentToken) {
setToken(currentToken);
try {
@@ -580,13 +592,29 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
window.localStorage.setItem(AUTH_SESSION_KEY, sessionId);
setToken(response.accessToken);
await warmUpAndApplySession(response.accessToken);
try {
await warmUpAndApplySession(response.accessToken);
} catch (error) {
if (!isGatewayUnavailableError(error)) {
throw error;
}
const cachedUser = readCachedUserProfile();
if (cachedUser) {
setUser(cachedUser);
}
clearPinLock();
setBootstrapError(null);
initialBootstrapDoneRef.current = true;
setIsApiReady(true);
setIsSessionReady(true);
setIsLoading(false);
}
if (pathname.startsWith('/auth/')) {
router.replace('/');
}
},
[pathname, router, warmUpAndApplySession]
[clearPinLock, pathname, router, warmUpAndApplySession]
);
const handlePinUnlock = React.useCallback(

View File

@@ -523,8 +523,6 @@ export async function refreshAuthSession(): Promise<RefreshSessionResponse> {
throw new ApiError('Refresh token недоступен на сервере', 401, 'NO_REFRESH');
}
await ensureApiGatewayReady();
const refreshToken = window.localStorage.getItem(AUTH_REFRESH_KEY);
const sessionId = window.localStorage.getItem(AUTH_SESSION_KEY);
if (!refreshToken) {
@@ -682,7 +680,7 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
}
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
const GATEWAY_RETRY_ATTEMPTS = 4;
const GATEWAY_RETRY_ATTEMPTS = 6;
const GATEWAY_STABLE_PROBE_INTERVAL_MS = 800;
const GATEWAY_STABLE_MAX_ROUNDS = 8;
const API_MAX_CONCURRENT = 6;
@@ -872,10 +870,6 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
}
export async function apiFetch<T>(path: string, options: RequestInit = {}, token?: string | null, allowRetry = true): Promise<T> {
if (typeof window !== 'undefined' && !gatewayReadyResolved && !path.startsWith('/health')) {
await ensureApiGatewayReady();
}
await acquireApiSlot();
const resolvedToken = resolveAccessToken(token);

View File

@@ -268,7 +268,7 @@ services:
container_name: lendry-id-nginx
restart: unless-stopped
environment:
IDP_API_GATEWAY_HOST: lendry-id-api-gateway
IDP_API_GATEWAY_HOST: api-gateway
IDP_API_GATEWAY_PORT: "3000"
IDP_FRONTEND_DOMAIN: ${DOMAIN_FRONTEND:-}
ports:
@@ -291,7 +291,7 @@ services:
test:
[
"CMD-SHELL",
"if [ -n \"$IDP_FRONTEND_DOMAIN\" ]; then wget -qO- --timeout=4 --no-check-certificate --header=\"Host: $IDP_FRONTEND_DOMAIN\" \"https://127.0.0.1/idp-api/health\" 2>/dev/null | grep -q '\"status\"' || wget -qO- --timeout=4 --header=\"Host: $IDP_FRONTEND_DOMAIN\" \"http://127.0.0.1/idp-api/health\" 2>/dev/null | grep -q '\"status\"' || exit 1; else wget -qO- --timeout=4 http://lendry-id-api-gateway:3000/health 2>/dev/null | grep -q '\"status\"' || exit 1; fi",
"if [ -n \"$IDP_FRONTEND_DOMAIN\" ]; then wget -qO- --timeout=4 --no-check-certificate --header=\"Host: $IDP_FRONTEND_DOMAIN\" \"https://127.0.0.1/idp-api/health\" 2>/dev/null | grep -q '\"status\"' || wget -qO- --timeout=4 --header=\"Host: $IDP_FRONTEND_DOMAIN\" \"http://127.0.0.1/idp-api/health\" 2>/dev/null | grep -q '\"status\"' || exit 1; else wget -qO- --timeout=4 http://api-gateway:3000/health 2>/dev/null | grep -q '\"status\"' || exit 1; fi",
]
interval: 15s
timeout: 8s

View File

@@ -4,7 +4,7 @@ set -eu
WATCH_INTERVAL="${IDP_NGINX_WATCH_INTERVAL:-5}"
WATCH_HOSTS="${IDP_NGINX_WATCH_HOSTS:-lendry-id-api-gateway:3000/health lendry-id-frontend:3000/}"
WATCH_HOSTS="${IDP_NGINX_WATCH_HOSTS:-api-gateway:3000/health frontend:3000/}"
log() {
echo "[idp-nginx-watch] $*"

View File

@@ -1705,19 +1705,19 @@ nginx_upstream() {
case "$1" in
api)
if [[ "$NGINX_MODE" == "docker" ]]; then
echo "http://lendry-id-api-gateway:3000"
echo "http://api-gateway:3000"
else
echo "http://127.0.0.1:3000"
fi
;;
frontend)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-frontend:3000" || echo "http://127.0.0.1:3002"
[[ "$NGINX_MODE" == "docker" ]] && echo "http://frontend:3000" || echo "http://127.0.0.1:3002"
;;
docs)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-docs:3000" || echo "http://127.0.0.1:3003"
[[ "$NGINX_MODE" == "docker" ]] && echo "http://docs:3000" || echo "http://127.0.0.1:3003"
;;
ws)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-media-ws:8085" || echo "http://127.0.0.1:8085"
[[ "$NGINX_MODE" == "docker" ]] && echo "http://media-ws:8085" || echo "http://127.0.0.1:8085"
;;
minio)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://minio:9000" || echo "http://127.0.0.1:9000"
@@ -2684,13 +2684,13 @@ validate_docker_api_nginx_config() {
fail "Конфиг ${conf} содержит legacy upstream{} — выполните ./install.sh --fix-all"
fi
if ! grep -q 'lendry-id-api-gateway:3000' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без proxy_pass на lendry-id-api-gateway:3000 — выполните ./install.sh --fix-all"
if ! grep -q 'api-gateway:3000' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без proxy_pass на api-gateway:3000 — выполните ./install.sh --fix-all"
fi
if grep -q 'proxy_pass.*\$request_uri' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} содержит proxy_pass \$request_uri — нужен ./install.sh --fix-all (502 на / без resolver)"
fi
ok "Конфиг API-домена: proxy_pass → lendry-id-api-gateway:3000"
ok "Конфиг API-домена: proxy_pass → api-gateway:3000"
}
validate_docker_frontend_nginx_config() {
@@ -2702,13 +2702,13 @@ validate_docker_frontend_nginx_config() {
fail "Конфиг ${conf} без location /idp-api — браузер SPA получит 502. Выполните ./install.sh --fix-all"
fi
if ! grep -q 'lendry-id-api-gateway:3000' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без proxy_pass на lendry-id-api-gateway:3000 — выполните ./install.sh --fix-all"
if ! grep -q 'api-gateway:3000' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без proxy_pass на api-gateway:3000 — выполните ./install.sh --fix-all"
fi
if ! grep -q 'location \^~ /idp-api/ws' "$conf" 2>/dev/null; then
warn "Конфиг ${conf}: location ^~ /idp-api/ws отсутствует — WebSocket может идти на api-gateway вместо media-ws"
fi
ok "Конфиг SSO-домена: /idp-api → lendry-id-api-gateway:3000"
ok "Конфиг SSO-домена: /idp-api → api-gateway:3000"
}
validate_all_docker_nginx_proxy_pass() {
@@ -2735,9 +2735,9 @@ verify_nginx_reaches_api_gateway() {
while [[ $attempt -lt $max_attempts ]]; do
gw_ip="$(docker_cmd inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' lendry-id-api-gateway 2>/dev/null || true)"
body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)"
wget -qO- --timeout=5 http://api-gateway:3000/health 2>/dev/null || true)"
if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then
ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})"
ok "Docker Nginx → api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})"
verify_nginx_idp_api_location || return 1
return 0
fi
@@ -2747,7 +2747,7 @@ verify_nginx_reaches_api_gateway() {
fi
done
warn "Docker Nginx не достучался до lendry-id-api-gateway:3000/health (IP: ${gw_ip:-?}, попыток: ${max_attempts})"
warn "Docker Nginx не достучался до api-gateway:3000/health (IP: ${gw_ip:-?}, попыток: ${max_attempts})"
warn "Проверьте: docker compose --profile proxy ps api-gateway nginx && docker compose logs --tail=40 api-gateway nginx"
return 1
}
@@ -2806,9 +2806,9 @@ verify_nginx_reaches_frontend() {
while [[ $attempt -lt $max_attempts ]]; do
fe_ip="$(docker_cmd inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' lendry-id-frontend 2>/dev/null || true)"
body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
wget -qO- --timeout=6 http://lendry-id-frontend:3000/ 2>/dev/null | head -c 256 || true)"
wget -qO- --timeout=6 http://frontend:3000/ 2>/dev/null | head -c 256 || true)"
if [[ -n "$body" ]]; then
ok "Docker Nginx → lendry-id-frontend:3000/ OK (IP frontend: ${fe_ip:-?})"
ok "Docker Nginx → frontend:3000/ OK (IP frontend: ${fe_ip:-?})"
if [[ -n "$frontend_domain" ]]; then
verify_nginx_frontend_root_location "$frontend_domain" || return 1
fi
@@ -2820,7 +2820,7 @@ verify_nginx_reaches_frontend() {
fi
done
warn "Docker Nginx не достучался до lendry-id-frontend:3000/ (IP: ${fe_ip:-?}, попыток: ${max_attempts})"
warn "Docker Nginx не достучался до frontend:3000/ (IP: ${fe_ip:-?}, попыток: ${max_attempts})"
warn "Проверьте: docker compose --env-file .env --profile proxy logs --tail=40 frontend nginx"
return 1
}
@@ -2873,7 +2873,7 @@ check_sso_frontend_root() {
return 0
elif [[ "$code" == "502" ]]; then
echo -e " ${RED}${NC} SSO / → 502 — Nginx не проксирует / на frontend (502 Bad Gateway в браузере)"
echo " Проверьте: docker compose --env-file .env --profile proxy exec nginx wget -qO- http://lendry-id-frontend:3000/"
echo " Проверьте: docker compose --env-file .env --profile proxy exec nginx wget -qO- http://frontend:3000/"
echo " Решение: docker compose --env-file .env --profile proxy restart nginx frontend"
return 1
else
@@ -2887,12 +2887,12 @@ verify_nginx_reaches_media_ws() {
build_compose_stack_cmd
local body
body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
wget -qO- --timeout=5 http://lendry-id-media-ws:8085/health 2>/dev/null || true)"
wget -qO- --timeout=5 http://media-ws:8085/health 2>/dev/null || true)"
if [[ "$body" == *ok* ]] || [[ "$body" == *healthy* ]]; then
ok "Docker Nginx → lendry-id-media-ws:8085/health OK"
ok "Docker Nginx → media-ws:8085/health OK"
return 0
fi
warn "Docker Nginx не достучался до lendry-id-media-ws:8085"
warn "Docker Nginx не достучался до media-ws:8085"
warn "Проверьте: docker compose ps media-ws && docker compose logs --tail=40 media-ws"
return 1
}
@@ -2906,12 +2906,12 @@ check_sso_idp_api_proxy() {
if docker_nginx_container_running; then
body="$(docker_cmd exec lendry-id-frontend \
wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)"
wget -qO- --timeout=5 http://api-gateway:3000/health 2>/dev/null || true)"
if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then
echo -e " ${GREEN}${NC} frontend → api-gateway:3000/health OK (fallback через Next.js rewrites)"
else
echo -e " ${RED}${NC} frontend → api-gateway:3000 недоступен (Next.js rewrites тоже дадут 502)"
echo " Проверьте: docker compose exec frontend wget -qO- http://lendry-id-api-gateway:3000/health"
echo " Проверьте: docker compose exec frontend wget -qO- http://api-gateway:3000/health"
return 1
fi
fi
@@ -2928,7 +2928,7 @@ check_sso_idp_api_proxy() {
elif [[ "$code" == "502" ]]; then
echo -e " ${RED}${NC} SSO /idp-api/health → 502 — Nginx не проксирует /idp-api на api-gateway"
echo " Проверьте: grep -E 'idp-api|idp_up_api|resolver' nginx/conf.d/${NGINX_PREFIX}-frontend.conf"
echo " docker compose --profile proxy exec nginx wget -qO- http://lendry-id-api-gateway:3000/health"
echo " docker compose --profile proxy exec nginx wget -qO- http://api-gateway:3000/health"
return 1
else
echo -e " ${YELLOW}!${NC} SSO /idp-api/health → HTTP ${code} (ожидалось 200)"
@@ -3353,7 +3353,7 @@ check_https_api_with_origin() {
elif [[ "$code" == "502" ]]; then
echo -e " ${RED}${NC} HTTPS API preflight (OPTIONS) → 502 — Nginx не достучался до api-gateway"
echo " Проверьте: grep resolver nginx/conf.d/lendry-id-api.conf"
echo " docker compose --profile proxy exec nginx wget -qO- http://lendry-id-api-gateway:3000/health"
echo " docker compose --profile proxy exec nginx wget -qO- http://api-gateway:3000/health"
return 1
else
echo -e " ${YELLOW}!${NC} HTTPS API preflight (OPTIONS) → HTTP ${code} (ожидалось 204)"
@@ -3435,8 +3435,8 @@ diagnose_common_issues() {
elif grep -q '172\.28\.50\.10:3000' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
echo -e " ${RED}${NC} Устаревший конфиг (статический IP 172.28.50.10) — выполните ./install.sh --fix-all"
issues=$((issues + 1))
elif grep -q 'lendry-id-api-gateway:3000' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
echo -e " ${GREEN}${NC} Nginx /idp-api: proxy_pass → lendry-id-api-gateway:3000"
elif grep -q 'api-gateway:3000' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
echo -e " ${GREEN}${NC} Nginx /idp-api: proxy_pass → api-gateway:3000"
else
echo -e " ${YELLOW}!${NC} Конфиг SSO без /idp-api proxy — выполните ./install.sh --fix-all"
issues=$((issues + 1))