From d8402f686e4845e365402453ca2cdb267d9b8330 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 4 Feb 2026 12:09:22 +0800 Subject: [PATCH] fix: base url in client (#31902) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web/service/client.spec.ts | 80 ++++++++++++++++++++++++++++++++++++++ web/service/client.ts | 28 ++++++++++++- 2 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 web/service/client.spec.ts diff --git a/web/service/client.spec.ts b/web/service/client.spec.ts new file mode 100644 index 0000000000..d8b46ad4b6 --- /dev/null +++ b/web/service/client.spec.ts @@ -0,0 +1,80 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const loadGetBaseURL = async (isClientValue: boolean) => { + vi.resetModules() + vi.doMock('@/utils/client', () => ({ isClient: isClientValue })) + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}) + // eslint-disable-next-line next/no-assign-module-variable + const module = await import('./client') + warnSpy.mockClear() + return { getBaseURL: module.getBaseURL, warnSpy } +} + +// Scenario: base URL selection and warnings. +describe('getBaseURL', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + // Scenario: client environment uses window origin. + it('should use window origin when running on the client', async () => { + // Arrange + const { origin } = window.location + const { getBaseURL, warnSpy } = await loadGetBaseURL(true) + + // Act + const url = getBaseURL('/api') + + // Assert + expect(url.href).toBe(`${origin}/api`) + expect(warnSpy).not.toHaveBeenCalled() + }) + + // Scenario: server environment falls back to localhost with warning. + it('should fall back to localhost and warn on the server', async () => { + // Arrange + const { getBaseURL, warnSpy } = await loadGetBaseURL(false) + + // Act + const url = getBaseURL('/api') + + // Assert + expect(url.href).toBe('http://localhost/api') + expect(warnSpy).toHaveBeenCalledTimes(1) + expect(warnSpy).toHaveBeenCalledWith('Using localhost as base URL in server environment, please configure accordingly.') + }) + + // Scenario: non-http protocols surface warnings. + it('should warn when protocol is not http or https', async () => { + // Arrange + const { getBaseURL, warnSpy } = await loadGetBaseURL(true) + + // Act + const url = getBaseURL('localhost:5001/console/api') + + // Assert + expect(url.protocol).toBe('localhost:') + expect(url.href).toBe('localhost:5001/console/api') + expect(warnSpy).toHaveBeenCalledTimes(1) + expect(warnSpy).toHaveBeenCalledWith( + 'Unexpected protocol for API requests, expected http or https. Current protocol: localhost:. Please configure accordingly.', + ) + }) + + // Scenario: absolute http URLs are preserved. + it('should keep absolute http URLs intact', async () => { + // Arrange + const { getBaseURL, warnSpy } = await loadGetBaseURL(true) + + // Act + const url = getBaseURL('https://api.example.com/console/api') + + // Assert + expect(url.href).toBe('https://api.example.com/console/api') + expect(warnSpy).not.toHaveBeenCalled() + }) +}) diff --git a/web/service/client.ts b/web/service/client.ts index c9c92ddd15..65c2b1fc68 100644 --- a/web/service/client.ts +++ b/web/service/client.ts @@ -13,12 +13,38 @@ import { consoleRouterContract, marketplaceRouterContract, } from '@/contract/router' +import { isClient } from '@/utils/client' import { request } from './base' const getMarketplaceHeaders = () => new Headers({ 'X-Dify-Version': !IS_MARKETPLACE ? APP_VERSION : '999.0.0', }) +function isURL(path: string) { + try { + // eslint-disable-next-line no-new + new URL(path) + return true + } + catch { + return false + } +} + +export function getBaseURL(path: string) { + const url = new URL(path, isURL(path) ? undefined : isClient ? window.location.origin : 'http://localhost') + + if (!isClient && !isURL(path)) { + console.warn('Using localhost as base URL in server environment, please configure accordingly.') + } + + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + console.warn(`Unexpected protocol for API requests, expected http or https. Current protocol: ${url.protocol}. Please configure accordingly.`) + } + + return url +} + const marketplaceLink = new OpenAPILink(marketplaceRouterContract, { url: MARKETPLACE_API_PREFIX, headers: () => (getMarketplaceHeaders()), @@ -39,7 +65,7 @@ export const marketplaceClient: JsonifiedClient { return request( input.url,