fix(desktop): normalize API base URL across renderer and main

Replace ad-hoc trim/fallback logic with a shared normalizer that strips
trailing slashes and accidental /api suffixes, rejects non-http schemes,
and rewrites Vite dev origins (517x) back to localhost:8080 so the
desktop client never points its API client at the renderer dev server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 12:56:47 +08:00
parent 7b4d7ccf68
commit 13464c7788
4 changed files with 71 additions and 10 deletions
@@ -0,0 +1,51 @@
const DEFAULT_LOCAL_API_BASE_URL = 'http://localhost:8080'
const VITE_DEV_PORT_PATTERN = /^517\d$/
function isLoopbackHost(hostname: string): boolean {
const normalized = hostname.toLowerCase()
return (
normalized === 'localhost' ||
normalized === '127.0.0.1' ||
normalized === '::1' ||
normalized === '[::1]'
)
}
function isViteDevOrigin(url: URL): boolean {
return isLoopbackHost(url.hostname) && VITE_DEV_PORT_PATTERN.test(url.port)
}
function cleanApiBaseURL(url: URL): string {
const pathname = url.pathname.replace(/\/+$/, '')
const normalizedPath = pathname.replace(/\/api$/i, '')
url.pathname = normalizedPath || '/'
url.search = ''
url.hash = ''
return url.toString().replace(/\/$/, '')
}
export function normalizeDesktopApiBaseURL(
value: string | null | undefined,
fallback = DEFAULT_LOCAL_API_BASE_URL,
): string {
const trimmed = String(value ?? '').trim()
const candidate = trimmed || fallback
try {
const parsed = new URL(candidate)
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
return normalizeDesktopApiBaseURL(fallback, DEFAULT_LOCAL_API_BASE_URL)
}
if (isViteDevOrigin(parsed)) {
return DEFAULT_LOCAL_API_BASE_URL
}
return cleanApiBaseURL(parsed)
} catch {
if (candidate === fallback) {
return DEFAULT_LOCAL_API_BASE_URL
}
return normalizeDesktopApiBaseURL(fallback, DEFAULT_LOCAL_API_BASE_URL)
}
}