fix(web): make marketplace URL builder SSR-safe (#37944)

This commit is contained in:
yyh 2026-06-26 11:35:22 +08:00 committed by GitHub
parent 677ab01806
commit 4cd8b8c733
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 54 additions and 5 deletions

View File

@ -162,7 +162,7 @@ export default function IntegrationsPage({
return
}
window.open(getMarketplaceUrl(marketplaceUrlPath), '_blank', 'noopener,noreferrer')
window.open(getMarketplaceUrl(marketplaceUrlPath, undefined, { source: window.location.origin }), '_blank', 'noopener,noreferrer')
}
const handleSelectSection = (nextSection: IntegrationSection) => {
if (onSectionChange) {

View File

@ -25,7 +25,7 @@ export default function IntegrationsSettingModal({
const { t } = useTranslation()
const isAgentSource = source === 'agent'
const handleSwitchToMarketplace = useCallback((path: string) => {
window.open(getMarketplaceUrl(path), '_blank', 'noopener,noreferrer')
window.open(getMarketplaceUrl(path, undefined, { source: window.location.origin }), '_blank', 'noopener,noreferrer')
}, [])
return (

View File

@ -1,3 +1,4 @@
import { describe, expect, it, vi } from 'vitest'
import { InputVarType } from '@/app/components/workflow/types'
import {
checkKey,
@ -207,6 +208,30 @@ describe('Variable Utilities', () => {
expect(url).toContain('category=ai')
expect(url).not.toContain('version=')
})
it('should include provided source without double encoding', () => {
const url = getMarketplaceUrl('/plugins', { category: 'ai' }, { source: 'https://example.com/app' })
expect(url).toContain('source=https%3A%2F%2Fexample.com')
expect(url).not.toContain('source=https%253A%252F%252Fexample.com')
})
it('should not access window during server render', () => {
const originalWindow = window
vi.stubGlobal('window', undefined)
try {
const url = getMarketplaceUrl('/plugins', { category: 'ai' })
expect(url).toContain('category=ai')
expect(url).not.toContain('source=')
}
finally {
vi.stubGlobal('window', originalWindow)
}
})
it('should not append empty query string when no params are available', () => {
expect(getMarketplaceUrl('/plugins')).toBe('/plugins')
})
})
describe('replaceSpaceWithUnderscoreInVarNameInput', () => {

View File

@ -132,8 +132,30 @@ export const getVars = (value: string) => {
// example: /dify
export const basePath = env.NEXT_PUBLIC_BASE_PATH
export function getMarketplaceUrl(path: string, params?: Record<string, string | undefined>) {
const searchParams = new URLSearchParams({ source: encodeURIComponent(window.location.origin) })
type MarketplaceUrlOptions = {
source?: string
}
const getUrlOrigin = (url?: string) => {
if (!url)
return undefined
try {
return new URL(url).origin
}
catch {
return undefined
}
}
const marketplaceSource = getUrlOrigin(env.NEXT_PUBLIC_WEB_PREFIX)
export function getMarketplaceUrl(path: string, params?: Record<string, string | undefined>, options?: MarketplaceUrlOptions) {
const searchParams = new URLSearchParams()
const source = getUrlOrigin(options?.source) ?? marketplaceSource
if (source)
searchParams.set('source', source)
if (params) {
Object.keys(params).forEach((key) => {
const value = params[key]
@ -141,7 +163,9 @@ export function getMarketplaceUrl(path: string, params?: Record<string, string |
searchParams.append(key, value)
})
}
return `${MARKETPLACE_URL_PREFIX}${path}?${searchParams.toString()}`
const queryString = searchParams.toString()
return queryString ? `${MARKETPLACE_URL_PREFIX}${path}?${queryString}` : `${MARKETPLACE_URL_PREFIX}${path}`
}
export const replaceSpaceWithUnderscoreInVarNameInput = (input: HTMLInputElement) => {