Compare commits

...

4 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
b3b20a999a Fix clearQueryParams to check window at runtime instead of using isServer constant
Replace the pre-evaluated isServer check with a direct typeof window === 'undefined' check to properly handle test scenarios where window is stubbed after module load.

Co-authored-by: lyzno1 <92089059+lyzno1@users.noreply.github.com>
2026-01-10 06:05:36 +00:00
copilot-swe-agent[bot]
cdf926cf82 Initial plan 2026-01-10 05:57:18 +00:00
yyh
e8c736afbf refactor(web): migrate embedded-chatbot header to use isClient utility
Missed migration from the initial refactoring - removes local isClient
variable definition and imports from the centralized utility.
2026-01-10 12:54:17 +08:00
yyh
6285a59508 refactor(web): extract isServer/isClient utility for consistent environment detection
Centralize server/client environment detection by introducing a dedicated utility
file instead of repeating `typeof window === 'undefined'` checks across the codebase.

This improves code maintainability and consistency across 8 files with 15 occurrences.

Closes #30802
2026-01-10 12:51:39 +08:00
9 changed files with 48 additions and 14 deletions

View File

@@ -29,6 +29,7 @@ import { CheckModal } from '@/hooks/use-pay'
import { useInfiniteAppList } from '@/service/use-apps'
import { AppModeEnum } from '@/types/app'
import { cn } from '@/utils/classnames'
import { isServer } from '@/utils/client'
import AppCard from './app-card'
import { AppCardSkeleton } from './app-card-skeleton'
import Empty from './empty'
@@ -71,7 +72,7 @@ const List = () => {
// 1) Normalize legacy/incorrect query params like ?mode=discover -> ?category=all
useEffect(() => {
// avoid running on server
if (typeof window === 'undefined')
if (isServer)
return
const mode = searchParams.get('mode')
if (!mode)

View File

@@ -11,6 +11,7 @@ import DifyLogo from '@/app/components/base/logo/dify-logo'
import Tooltip from '@/app/components/base/tooltip'
import { useGlobalPublicStore } from '@/context/global-public-context'
import { cn } from '@/utils/classnames'
import { isClient } from '@/utils/client'
import {
useEmbeddedChatbotContext,
} from '../context'
@@ -40,7 +41,6 @@ const Header: FC<IHeaderProps> = ({
allInputsHidden,
} = useEmbeddedChatbotContext()
const isClient = typeof window !== 'undefined'
const isIframe = isClient ? window.self !== window.top : false
const [parentOrigin, setParentOrigin] = useState('')
const [showToggleExpandButton, setShowToggleExpandButton] = useState(false)

View File

@@ -13,6 +13,7 @@ import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { isServer } from '@/utils/client'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
@@ -49,14 +50,14 @@ const FeaturedTools = ({
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@@ -64,7 +65,7 @@ const FeaturedTools = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@@ -12,6 +12,7 @@ import Tooltip from '@/app/components/base/tooltip'
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
import Action from '@/app/components/workflow/block-selector/market-place-plugin/action'
import { useGetLanguage } from '@/context/i18n'
import { isServer } from '@/utils/client'
import { formatNumber } from '@/utils/format'
import { getMarketplaceUrl } from '@/utils/var'
import BlockIcon from '../block-icon'
@@ -42,14 +43,14 @@ const FeaturedTriggers = ({
const language = useGetLanguage()
const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT)
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@@ -57,7 +58,7 @@ const FeaturedTriggers = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@@ -11,6 +11,7 @@ import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid
import Loading from '@/app/components/base/loading'
import { getFormattedPlugin } from '@/app/components/plugins/marketplace/utils'
import { useRAGRecommendedPlugins } from '@/service/use-tools'
import { isServer } from '@/utils/client'
import { getMarketplaceUrl } from '@/utils/var'
import List from './list'
@@ -29,14 +30,14 @@ const RAGToolRecommendations = ({
}: RAGToolRecommendationsProps) => {
const { t } = useTranslation()
const [isCollapsed, setIsCollapsed] = useState<boolean>(() => {
if (typeof window === 'undefined')
if (isServer)
return false
const stored = window.localStorage.getItem(STORAGE_KEY)
return stored === 'true'
})
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
const stored = window.localStorage.getItem(STORAGE_KEY)
if (stored !== null)
@@ -44,7 +45,7 @@ const RAGToolRecommendations = ({
}, [])
useEffect(() => {
if (typeof window === 'undefined')
if (isServer)
return
window.localStorage.setItem(STORAGE_KEY, String(isCollapsed))
}, [isCollapsed])

View File

@@ -5,6 +5,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { NUM_INFINITE } from '@/app/components/billing/config'
import { Plan } from '@/app/components/billing/type'
import { IS_CLOUD_EDITION } from '@/config'
import { isServer } from '@/utils/client'
export type TriggerEventsLimitModalPayload = {
usage: number
@@ -46,7 +47,7 @@ export const useTriggerEventsLimitModal = ({
useEffect(() => {
if (!IS_CLOUD_EDITION)
return
if (typeof window === 'undefined')
if (isServer)
return
if (!currentWorkspaceId)
return

View File

@@ -5,12 +5,13 @@ import type { FC, PropsWithChildren } from 'react'
import { QueryClientProvider } from '@tanstack/react-query'
import { useState } from 'react'
import { TanStackDevtoolsLoader } from '@/app/components/devtools/tanstack/loader'
import { isServer } from '@/utils/client'
import { makeQueryClient } from './query-client-server'
let browserQueryClient: QueryClient | undefined
function getQueryClient() {
if (typeof window === 'undefined') {
if (isServer) {
return makeQueryClient()
}
if (!browserQueryClient)

26
web/utils/client.ts Normal file
View File

@@ -0,0 +1,26 @@
/**
* Server/Client environment detection utilities
*
* Use these constants and functions to safely detect the runtime environment
* in Next.js applications where code may execute on both server and client.
*/
/**
* Check if code is running on server-side (SSR)
*
* @example
* if (isServer) {
* // Server-only logic
* }
*/
export const isServer = typeof window === 'undefined'
/**
* Check if code is running on client-side (browser)
*
* @example
* if (isClient) {
* localStorage.setItem('key', 'value')
* }
*/
export const isClient = typeof window !== 'undefined'

View File

@@ -1,3 +1,5 @@
import { isServer } from '@/utils/client'
/**
* Send Google Analytics event
* @param eventName - event name
@@ -7,7 +9,7 @@ export const sendGAEvent = (
eventName: string,
eventParams?: GtagEventParams,
): void => {
if (typeof window === 'undefined' || typeof (window as any).gtag !== 'function') {
if (isServer || typeof (window as any).gtag !== 'function') {
return
}
(window as any).gtag('event', eventName, eventParams)