Merge pull request #226 from iflytek/fix/issue-223-nginx-prefix

fix(web): preserve nginx subpath prefix for api/runtime config
This commit is contained in:
dongmucat 2026-04-07 10:26:48 +08:00 • committed by GitHub
commit 8cb783406d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 49 additions and 5 deletions

View file

@ -36,6 +36,7 @@ vi.mock('@/shared/lib/api-error', () => ({
import {
WEB_API_PREFIX,
buildApiUrl,
fetchText,
getDirectAuthRuntimeConfig,
getSessionBootstrapRuntimeConfig,
} from './client'
@ -45,6 +46,8 @@ beforeEach(() => {
})
afterEach(() => {
vi.unstubAllGlobals()
if (originalWindow) {
Object.defineProperty(globalThis, 'window', {
configurable: true,
@ -79,6 +82,38 @@ describe('buildApiUrl', () => {
const url = buildApiUrl('/api/v1/auth/me')
expect(url).toBe('https://api.example.com/api/v1/auth/me')
})
it('preserves base URL path prefixes', () => {
window.__SKILLHUB_RUNTIME_CONFIG__ = { apiBaseUrl: 'https://api.example.com/skill_hub' }
const url = buildApiUrl('/api/v1/auth/me')
expect(url).toBe('https://api.example.com/skill_hub/api/v1/auth/me')
})
it('supports relative base URL path prefixes', () => {
window.__SKILLHUB_RUNTIME_CONFIG__ = { apiBaseUrl: '/skill_hub' }
const url = buildApiUrl('/api/v1/auth/me')
expect(url).toBe('/skill_hub/api/v1/auth/me')
})
})
describe('fetchText', () => {
it('applies base URL path prefixes for fetch requests', async () => {
window.__SKILLHUB_RUNTIME_CONFIG__ = { apiBaseUrl: 'https://api.example.com/skill_hub' }
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
text: async () => 'ok',
})
vi.stubGlobal('fetch', fetchMock)
await fetchText('/api/v1/auth/me')
expect(fetchMock).toHaveBeenCalledWith(
'https://api.example.com/skill_hub/api/v1/auth/me',
expect.objectContaining({
headers: expect.any(Headers),
}),
)
})
})
describe('getDirectAuthRuntimeConfig', () => {

View file

@ -252,7 +252,7 @@ function withBaseUrl(input: RequestInfo | URL): RequestInfo | URL {
if (!baseUrl || typeof input !== 'string' || !input.startsWith('/')) {
return input
}
return new URL(input, ensureTrailingSlash(baseUrl))
return prependApiBaseUrl(baseUrl, input)
}
export function buildApiUrl(path: string): string {
@ -260,11 +260,20 @@ export function buildApiUrl(path: string): string {
if (!baseUrl) {
return path
}
return new URL(path, ensureTrailingSlash(baseUrl)).toString()
return prependApiBaseUrl(baseUrl, path)
}
function ensureTrailingSlash(value: string): string {
return value.endsWith('/') ? value : `${value}/`
function prependApiBaseUrl(baseUrl: string, path: string): string {
const normalizedBaseUrl = trimTrailingSlash(baseUrl)
const normalizedPath = path.startsWith('/') ? path : `/${path}`
return `${normalizedBaseUrl}${normalizedPath}`
}
function trimTrailingSlash(value: string): string {
if (value.length > 1 && value.endsWith('/')) {
return value.slice(0, -1)
}
return value
}
export async function getCurrentUser(): Promise<User | null> {

View file

@ -7,7 +7,7 @@
async function loadRuntimeConfig() {
await new Promise<void>((resolve, reject) => {
const script = document.createElement('script')
script.src = '/runtime-config.js'
script.src = new URL('../runtime-config.js', import.meta.url).toString()
script.async = false
script.onload = () => resolve()
script.onerror = () => reject(new Error('Failed to load runtime config'))