diff --git a/apps/frontend/lib/api.ts b/apps/frontend/lib/api.ts index 9c5c77e..ec2c2a3 100644 --- a/apps/frontend/lib/api.ts +++ b/apps/frontend/lib/api.ts @@ -21,6 +21,10 @@ function usesSameOriginWsProxy(): boolean { return configuredWsUrl.includes('/idp-api/'); } +function isLocalDevApiUrl(url: string): boolean { + return /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/i.test(url); +} + function resolveBrowserApiBaseUrl(): string { // SSR: внутренний URL Docker-сети (api-gateway), не публичный домен. if (typeof window === 'undefined') { @@ -31,12 +35,17 @@ function resolveBrowserApiBaseUrl(): string { return configuredApiUrl.replace(/\/+$/, '') || API_ORIGIN_PROXY_PREFIX; } - // Split-domain (https://api.example.com): браузер ходит напрямую на API-домен. - // Same-origin (https://sso.example.com/idp-api): через /idp-api и host/nginx rewrites. if (usesSameOriginApiProxy()) { return API_ORIGIN_PROXY_PREFIX; } + // Split-domain (SSO на sso.*, API на api.*): браузер SPA ходит same-origin через + // /idp-api на домене SSO. PUBLIC_API_URL=https://api.* остаётся для OAuth issuer и + // внешних клиентов — cross-origin fetch из SPA не нужен и ломается (CORS/502). + if (!isLocalDevApiUrl(configuredApiUrl)) { + return API_ORIGIN_PROXY_PREFIX; + } + return configuredApiUrl; } @@ -50,6 +59,11 @@ function resolveBrowserWsBaseUrl(): string { return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`; } + if (!/^wss?:\/\/(localhost|127\.0\.0\.1)/i.test(configuredWsUrl)) { + const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; + return `${scheme}//${window.location.host}${API_ORIGIN_PROXY_PREFIX}/ws`; + } + return configuredWsUrl; } diff --git a/apps/frontend/next.config.ts b/apps/frontend/next.config.ts index 3264bac..a79f3d6 100644 --- a/apps/frontend/next.config.ts +++ b/apps/frontend/next.config.ts @@ -24,6 +24,10 @@ const nextConfig: NextConfig = { source: '/idp-api/:path*', destination: `${internalApiUrl}/:path*` }, + { + source: '/auth/:path*', + destination: `${internalApiUrl}/auth/:path*` + }, { source: '/oauth/:path*', destination: `${internalApiUrl}/oauth/:path*` diff --git a/install.sh b/install.sh index 4845ca9..0429e72 100644 --- a/install.sh +++ b/install.sh @@ -10,7 +10,7 @@ set -euo pipefail -SCRIPT_VERSION="2.3.5" +SCRIPT_VERSION="2.3.6" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" @@ -1876,7 +1876,7 @@ nginx_proxy_headers=" proxy_set_header Host \$host; # Встроенный DNS Docker. В docker-режиме nginx обязан перерезолвлять имена # бэкенд-контейнеров на каждый запрос — иначе после пересборки контейнера # (новый IP) остаётся закешированный старый адрес и все ответы становятся 502. -NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=10s;" +NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=1s;" # Имя nginx-переменной из ключа апстрима (только [A-Za-z0-9_]). nginx_upstream_var() { @@ -3216,10 +3216,13 @@ action_fix_all_errors() { wait_for_service "api-gateway" 90 wait_for_api_grpc_ready 90 || compose_build_and_up api-gateway - log "Шаг 4/6: пересоздание edge-сервисов (frontend, docs, media-ws)..." + log "Шаг 4/6: пересборка frontend (same-origin /idp-api), docs, media-ws..." compose_build_and_up frontend docs media-ws - wait_for_service "frontend" 60 + wait_for_service "frontend" 90 wait_for_service "docs" 60 + local baked_api + baked_api="$(docker_cmd exec lendry-id-frontend printenv NEXT_PUBLIC_API_URL 2>/dev/null || true)" + log "frontend NEXT_PUBLIC_API_URL=${baked_api:-не задан} (браузер SPA использует /idp-api на SSO)" log "Шаг 5/6: Docker Nginx — конфиги в ./nginx/conf.d и контейнер lendry-id-nginx..." local ssl_type @@ -3257,8 +3260,9 @@ action_fix_all_errors() { api_domain="$(env_get DOMAIN_API "")" frontend_domain="$(env_get DOMAIN_FRONTEND "")" if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then - echo " Split-domain: в DevTools → Network запросы должны идти на https://${api_domain}/...," - echo " а не на https://${frontend_domain}/idp-api/..." + echo " Split-domain: в DevTools → Network запросы SPA должны идти на" + echo " https://${frontend_domain}/idp-api/... (same-origin), а не на https://${api_domain}/..." + echo " PUBLIC_API_URL=https://${api_domain} — для OAuth issuer и внешних клиентов." echo " После пересборки frontend обновите страницу с полным сбросом кеша (Ctrl+Shift+R)." else echo " Проверьте в браузере — запросы к /idp-api/* должны отвечать 200."