diff --git a/web/app/components/billing/partner-stack/__tests__/cookie-recorder.spec.tsx b/web/app/components/billing/partner-stack/__tests__/cookie-recorder.spec.tsx
new file mode 100644
index 0000000000..1441653c9c
--- /dev/null
+++ b/web/app/components/billing/partner-stack/__tests__/cookie-recorder.spec.tsx
@@ -0,0 +1,45 @@
+import { render } from '@testing-library/react'
+import PartnerStackCookieRecorder from '../cookie-recorder'
+
+let isCloudEdition = true
+
+const saveOrUpdate = vi.fn()
+
+vi.mock('@/config', () => ({
+ get IS_CLOUD_EDITION() {
+ return isCloudEdition
+ },
+}))
+
+vi.mock('../use-ps-info', () => ({
+ default: () => ({
+ saveOrUpdate,
+ }),
+}))
+
+describe('PartnerStackCookieRecorder', () => {
+ beforeEach(() => {
+ vi.clearAllMocks()
+ isCloudEdition = true
+ })
+
+ it('should call saveOrUpdate once on mount when running in cloud edition', () => {
+ render()
+
+ expect(saveOrUpdate).toHaveBeenCalledTimes(1)
+ })
+
+ it('should not call saveOrUpdate when not running in cloud edition', () => {
+ isCloudEdition = false
+
+ render()
+
+ expect(saveOrUpdate).not.toHaveBeenCalled()
+ })
+
+ it('should render null', () => {
+ const { container } = render()
+
+ expect(container.innerHTML).toBe('')
+ })
+})
diff --git a/web/app/components/billing/partner-stack/cookie-recorder.tsx b/web/app/components/billing/partner-stack/cookie-recorder.tsx
new file mode 100644
index 0000000000..3c75b2973c
--- /dev/null
+++ b/web/app/components/billing/partner-stack/cookie-recorder.tsx
@@ -0,0 +1,19 @@
+'use client'
+
+import { useEffect } from 'react'
+import { IS_CLOUD_EDITION } from '@/config'
+import usePSInfo from './use-ps-info'
+
+const PartnerStackCookieRecorder = () => {
+ const { saveOrUpdate } = usePSInfo()
+
+ useEffect(() => {
+ if (!IS_CLOUD_EDITION)
+ return
+ saveOrUpdate()
+ }, [])
+
+ return null
+}
+
+export default PartnerStackCookieRecorder
diff --git a/web/app/components/billing/partner-stack/use-ps-info.ts b/web/app/components/billing/partner-stack/use-ps-info.ts
index 7c45d7ef87..5a83dec0e5 100644
--- a/web/app/components/billing/partner-stack/use-ps-info.ts
+++ b/web/app/components/billing/partner-stack/use-ps-info.ts
@@ -24,7 +24,7 @@ const usePSInfo = () => {
}] = useBoolean(false)
const { mutateAsync } = useBindPartnerStackInfo()
// Save to top domain. cloud.dify.ai => .dify.ai
- const domain = globalThis.location.hostname.replace('cloud', '')
+ const domain = globalThis.location?.hostname.replace('cloud', '')
const saveOrUpdate = useCallback(() => {
if (!psPartnerKey || !psClickId)
@@ -39,7 +39,7 @@ const usePSInfo = () => {
path: '/',
domain,
})
- }, [psPartnerKey, psClickId, isPSChanged])
+ }, [psPartnerKey, psClickId, isPSChanged, domain])
const bind = useCallback(async () => {
if (psPartnerKey && psClickId && !hasBind) {
@@ -59,7 +59,7 @@ const usePSInfo = () => {
Cookies.remove(PARTNER_STACK_CONFIG.cookieName, { path: '/', domain })
setBind()
}
- }, [psPartnerKey, psClickId, mutateAsync, hasBind, setBind])
+ }, [psPartnerKey, psClickId, hasBind, domain, setBind, mutateAsync])
return {
psPartnerKey,
psClickId,
diff --git a/web/app/layout.tsx b/web/app/layout.tsx
index f08ce9bb49..98cce27491 100644
--- a/web/app/layout.tsx
+++ b/web/app/layout.tsx
@@ -9,6 +9,7 @@ import { getLocaleOnServer } from '@/i18n-config/server'
import { ToastProvider } from './components/base/toast'
import { ToastHost } from './components/base/ui/toast'
import { TooltipProvider } from './components/base/ui/tooltip'
+import PartnerStackCookieRecorder from './components/billing/partner-stack/cookie-recorder'
import { AgentationLoader } from './components/devtools/agentation-loader'
import { ReactScanLoader } from './components/devtools/react-scan/loader'
import { I18nServerProvider } from './components/provider/i18n-server'
@@ -67,6 +68,7 @@ const LocaleLayout = async ({
+
diff --git a/web/app/signin/page.tsx b/web/app/signin/page.tsx
index 7fad92fe5d..3f893b12fa 100644
--- a/web/app/signin/page.tsx
+++ b/web/app/signin/page.tsx
@@ -1,18 +1,11 @@
'use client'
-import { useEffect } from 'react'
import { useSearchParams } from '@/next/navigation'
-import usePSInfo from '../components/billing/partner-stack/use-ps-info'
import NormalForm from './normal-form'
import OneMoreStep from './one-more-step'
const SignIn = () => {
const searchParams = useSearchParams()
const step = searchParams.get('step')
- const { saveOrUpdate } = usePSInfo()
-
- useEffect(() => {
- saveOrUpdate()
- }, [])
if (step === 'next')
return