Keep desktop smoke on the configured backend

Browser-mode startup now respects an explicit Vite desktop server URL before falling back to same-origin H5 serving. This keeps local desktop smoke tests pointed at the real API server while preserving same-origin behavior for packaged H5 access.

Constraint: Vite dev smoke serves the web app and API on different loopback ports
Rejected: Treat same-origin as always preferred | Vite would answer /api requests with HTML and the chat surface never becomes ready
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/lib/desktopRuntime.test.ts src/api/client.test.ts --run
Tested: bun run check:desktop
Tested: bun run quality:gate --mode baseline --allow-live --only 'provider-smoke:*' --only 'desktop-smoke:*' --provider-model codingplan:main:codingplan-main
Not-tested: Full bun run quality:pr after this commit; it will run again during push
This commit is contained in:
程序员阿江(Relakkes) 2026-05-10 23:01:43 +08:00
parent d37d752310
commit d1211e4dca
3 changed files with 44 additions and 2 deletions

View File

@ -44,6 +44,10 @@ export function getDefaultBaseUrl() {
return DEFAULT_BASE_URL
}
export function hasExplicitDefaultBaseUrl() {
return Boolean(ENV_BASE_URL)
}
export class ApiError extends Error {
constructor(
public status: number,

View File

@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
const clientMocks = vi.hoisted(() => ({
defaultBaseUrl: 'http://127.0.0.1:3456',
explicitDefaultBaseUrl: false,
setBaseUrl: vi.fn(),
setAuthToken: vi.fn(),
postVerify: vi.fn(),
@ -12,6 +13,7 @@ vi.mock('../api/client', () => ({
post: clientMocks.postVerify,
},
getDefaultBaseUrl: () => clientMocks.defaultBaseUrl,
hasExplicitDefaultBaseUrl: () => clientMocks.explicitDefaultBaseUrl,
setAuthToken: clientMocks.setAuthToken,
setBaseUrl: clientMocks.setBaseUrl,
}))
@ -31,6 +33,8 @@ describe('desktopRuntime browser H5 bootstrap', () => {
beforeEach(() => {
vi.clearAllMocks()
clientMocks.defaultBaseUrl = 'http://127.0.0.1:3456'
clientMocks.explicitDefaultBaseUrl = false
vi.useRealTimers()
window.localStorage.clear()
window.history.pushState({}, '', '/')
@ -111,6 +115,26 @@ describe('desktopRuntime browser H5 bootstrap', () => {
})
})
it('prefers an explicit Vite desktop server URL over the dev server origin', async () => {
clientMocks.defaultBaseUrl = 'http://127.0.0.1:55189'
clientMocks.explicitDefaultBaseUrl = true
window.history.pushState({}, '', '/')
globalThis.fetch = vi.fn().mockResolvedValue(
new Response(null, { status: 200 }),
) as typeof fetch
await expect(initializeDesktopServerUrl()).resolves.toBe('http://127.0.0.1:55189')
expect(clientMocks.setBaseUrl).toHaveBeenLastCalledWith('http://127.0.0.1:55189')
expect(clientMocks.setAuthToken).toHaveBeenLastCalledWith(null)
expect(globalThis.fetch).toHaveBeenCalledWith('http://127.0.0.1:55189/health', {
cache: 'no-store',
})
expect(globalThis.fetch).toHaveBeenCalledWith('http://127.0.0.1:55189/api/status', {
cache: 'no-store',
})
})
it('normalizes unreachable remote browser startup into a recoverable H5 error', async () => {
vi.useFakeTimers()
globalThis.fetch = vi.fn().mockRejectedValue(new TypeError('Failed to fetch')) as typeof fetch

View File

@ -1,4 +1,10 @@
import { api, getDefaultBaseUrl, setAuthToken, setBaseUrl } from '../api/client'
import {
api,
getDefaultBaseUrl,
hasExplicitDefaultBaseUrl,
setAuthToken,
setBaseUrl,
} from '../api/client'
export const H5_SERVER_URL_STORAGE_KEY = 'cc-haha-h5-server-url'
export const H5_TOKEN_STORAGE_KEY = 'cc-haha-h5-token'
@ -133,7 +139,7 @@ async function initializeBrowserServerUrl(fallbackUrl: string) {
const requestedUrl =
normalizeServerUrl(queryUrl) ??
stored.serverUrl ??
getSameOriginServerUrl() ??
getConfiguredBrowserServerUrl(fallbackUrl) ??
fallbackUrl
const token = stored.token
const browserH5Runtime = requiresH5AuthForServerUrl(requestedUrl)
@ -249,6 +255,14 @@ function getSameOriginServerUrl() {
return normalizeServerUrl(window.location.origin)
}
function getConfiguredBrowserServerUrl(fallbackUrl: string) {
if (hasExplicitDefaultBaseUrl()) {
return normalizeServerUrl(fallbackUrl)
}
return getSameOriginServerUrl()
}
export function isLoopbackHostname(hostname: string) {
const normalized = hostname.trim().replace(/^\[/, '').replace(/\]$/, '').toLowerCase()
return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '::1'