diff --git a/frontend/src/config/apiBase.ts b/frontend/src/config/apiBase.ts index 6459e6b..2233ae0 100644 --- a/frontend/src/config/apiBase.ts +++ b/frontend/src/config/apiBase.ts @@ -1,123 +1,12 @@ -/** - * API 基础配置:自动探测内网/公网后端,失败时回退公网 - */ const API_PREFIX = '/api/v1' -const LAN_BACKEND_ORIGIN = import.meta.env.VITE_BACKEND_ORIGIN -const PUBLIC_BACKEND_ORIGIN = import.meta.env.VITE_BACKEND_ORIGIN -const PROBE_TIMEOUT_MS = 1200 -console.log(LAN_BACKEND_ORIGIN); - -const LAN_API_BASE_URL = `${LAN_BACKEND_ORIGIN}${API_PREFIX}` -const PUBLIC_API_BASE_URL = `${PUBLIC_BACKEND_ORIGIN}${API_PREFIX}` -const ENV_API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string | undefined - -const EXPECTED_OPENAPI_PATHS = ['/api/v1/auth/login', '/api/v1/events/unified'] - -let detectedApiBaseUrl: string | null = ENV_API_BASE_URL ?? null -let detectPromise: Promise | null = null - -function normalizePath(path: string): string { - if (!path) return '/' - return path.startsWith('/') ? path : `/${path}` -} - -function buildUrl(base: string, path: string): string { - return `${base}${normalizePath(path)}` -} - -function isPrivateIpv4(hostname: string): boolean { - const parts = hostname.split('.').map((part) => Number.parseInt(part, 10)) - if (parts.length !== 4 || parts.some((part) => Number.isNaN(part) || part < 0 || part > 255)) { - return false +export function buildUrl(path: string): string { + if (!path.startsWith('/')) { + path = '/' + path } - - const a = parts[0] as number - const b = parts[1] as number - if (a === 10) return true - if (a === 172 && b >= 16 && b <= 31) return true - if (a === 192 && b === 168) return true - if (a === 127) return true - return false + return `${API_PREFIX}${path}` } -function isLanHostname(hostname: string): boolean { - const normalized = hostname.toLowerCase() - if (normalized === 'localhost' || normalized.endsWith('.local')) return true - return isPrivateIpv4(normalized) -} - -// 探测内网后端是否可用(请求 openapi.json) -async function probeLanBackend(): Promise { - if (typeof window === 'undefined') return false - - const controller = new AbortController() - const timeout = window.setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS) - try { - const response = await fetch(`${LAN_BACKEND_ORIGIN}/openapi.json`, { - method: 'GET', - cache: 'no-store', - signal: controller.signal, - }) - if (!response.ok) return false - - const data = (await response.json()) as { paths?: Record } - const paths = data.paths - if (!paths || typeof paths !== 'object') return false - - return EXPECTED_OPENAPI_PATHS.every((path) => - Object.prototype.hasOwnProperty.call(paths, path), - ) - } catch { - return false - } finally { - window.clearTimeout(timeout) - } -} - -// 根据当前 hostname 与探测结果选择内网或公网 API 地址 -async function detectApiBaseUrl(): Promise { - if (ENV_API_BASE_URL) return ENV_API_BASE_URL - if (typeof window === 'undefined') return PUBLIC_API_BASE_URL - - if (!isLanHostname(window.location.hostname)) { - return PUBLIC_API_BASE_URL - } - - const canUseLan = await probeLanBackend() - return canUseLan ? LAN_API_BASE_URL : PUBLIC_API_BASE_URL -} - -function isLikelyNetworkError(error: unknown): boolean { - return error instanceof TypeError || (error instanceof DOMException && error.name === 'AbortError') -} - -export async function getApiBaseUrl(): Promise { - if (detectedApiBaseUrl) return detectedApiBaseUrl - if (!detectPromise) { - detectPromise = detectApiBaseUrl() - .then((url) => { - detectedApiBaseUrl = url - return url - }) - .finally(() => { - detectPromise = null - }) - } - return detectPromise -} - -export async function fetchApi(path: string, init?: RequestInit): Promise { - const apiBaseUrl = await getApiBaseUrl() - const requestUrl = buildUrl(apiBaseUrl, path) - - try { - return await fetch(requestUrl, init) - } catch (error) { - if (!ENV_API_BASE_URL && apiBaseUrl === LAN_API_BASE_URL && isLikelyNetworkError(error)) { - detectedApiBaseUrl = PUBLIC_API_BASE_URL - return fetch(buildUrl(PUBLIC_API_BASE_URL, path), init) - } - throw error - } +export async function fetchApi(path: string, init?: RequestInit) { + return fetch(buildUrl(path), init) }