mirror of
https://github.com/langgenius/dify.git
synced 2026-04-24 17:16:37 +08:00
Fix/27468 in dify 192 the iframe embed cannot pass the user id in system variable (#27524)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
d9860b8907
commit
29afc0657d
132
web/__tests__/embedded-user-id-auth.test.tsx
Normal file
132
web/__tests__/embedded-user-id-auth.test.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
|
|
||||||
|
import MailAndPasswordAuth from '@/app/(shareLayout)/webapp-signin/components/mail-and-password-auth'
|
||||||
|
import CheckCode from '@/app/(shareLayout)/webapp-signin/check-code/page'
|
||||||
|
|
||||||
|
jest.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string) => key,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const replaceMock = jest.fn()
|
||||||
|
const backMock = jest.fn()
|
||||||
|
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
usePathname: jest.fn(() => '/chatbot/test-app'),
|
||||||
|
useRouter: jest.fn(() => ({
|
||||||
|
replace: replaceMock,
|
||||||
|
back: backMock,
|
||||||
|
})),
|
||||||
|
useSearchParams: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const mockStoreState = {
|
||||||
|
embeddedUserId: 'embedded-user-99',
|
||||||
|
shareCode: 'test-app',
|
||||||
|
}
|
||||||
|
|
||||||
|
const useWebAppStoreMock = jest.fn((selector?: (state: typeof mockStoreState) => any) => {
|
||||||
|
return selector ? selector(mockStoreState) : mockStoreState
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@/context/web-app-context', () => ({
|
||||||
|
useWebAppStore: (selector?: (state: typeof mockStoreState) => any) => useWebAppStoreMock(selector),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const webAppLoginMock = jest.fn()
|
||||||
|
const webAppEmailLoginWithCodeMock = jest.fn()
|
||||||
|
const sendWebAppEMailLoginCodeMock = jest.fn()
|
||||||
|
|
||||||
|
jest.mock('@/service/common', () => ({
|
||||||
|
webAppLogin: (...args: any[]) => webAppLoginMock(...args),
|
||||||
|
webAppEmailLoginWithCode: (...args: any[]) => webAppEmailLoginWithCodeMock(...args),
|
||||||
|
sendWebAppEMailLoginCode: (...args: any[]) => sendWebAppEMailLoginCodeMock(...args),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const fetchAccessTokenMock = jest.fn()
|
||||||
|
|
||||||
|
jest.mock('@/service/share', () => ({
|
||||||
|
fetchAccessToken: (...args: any[]) => fetchAccessTokenMock(...args),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const setWebAppAccessTokenMock = jest.fn()
|
||||||
|
const setWebAppPassportMock = jest.fn()
|
||||||
|
|
||||||
|
jest.mock('@/service/webapp-auth', () => ({
|
||||||
|
setWebAppAccessToken: (...args: any[]) => setWebAppAccessTokenMock(...args),
|
||||||
|
setWebAppPassport: (...args: any[]) => setWebAppPassportMock(...args),
|
||||||
|
webAppLogout: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/app/components/signin/countdown', () => () => <div data-testid="countdown" />)
|
||||||
|
|
||||||
|
jest.mock('@remixicon/react', () => ({
|
||||||
|
RiMailSendFill: () => <div data-testid="mail-icon" />,
|
||||||
|
RiArrowLeftLine: () => <div data-testid="arrow-icon" />,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { useSearchParams } = jest.requireMock('next/navigation') as {
|
||||||
|
useSearchParams: jest.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('embedded user id propagation in authentication flows', () => {
|
||||||
|
it('passes embedded user id when logging in with email and password', async () => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set('redirect_url', encodeURIComponent('/chatbot/test-app'))
|
||||||
|
useSearchParams.mockReturnValue(params)
|
||||||
|
|
||||||
|
webAppLoginMock.mockResolvedValue({ result: 'success', data: { access_token: 'login-token' } })
|
||||||
|
fetchAccessTokenMock.mockResolvedValue({ access_token: 'passport-token' })
|
||||||
|
|
||||||
|
render(<MailAndPasswordAuth isEmailSetup />)
|
||||||
|
|
||||||
|
fireEvent.change(screen.getByLabelText('login.email'), { target: { value: 'user@example.com' } })
|
||||||
|
fireEvent.change(screen.getByLabelText(/login\.password/), { target: { value: 'strong-password' } })
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'login.signBtn' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(fetchAccessTokenMock).toHaveBeenCalledWith({
|
||||||
|
appCode: 'test-app',
|
||||||
|
userId: 'embedded-user-99',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
expect(setWebAppAccessTokenMock).toHaveBeenCalledWith('login-token')
|
||||||
|
expect(setWebAppPassportMock).toHaveBeenCalledWith('test-app', 'passport-token')
|
||||||
|
expect(replaceMock).toHaveBeenCalledWith('/chatbot/test-app')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('passes embedded user id when verifying email code', async () => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
params.set('redirect_url', encodeURIComponent('/chatbot/test-app'))
|
||||||
|
params.set('email', encodeURIComponent('user@example.com'))
|
||||||
|
params.set('token', encodeURIComponent('token-abc'))
|
||||||
|
useSearchParams.mockReturnValue(params)
|
||||||
|
|
||||||
|
webAppEmailLoginWithCodeMock.mockResolvedValue({ result: 'success', data: { access_token: 'code-token' } })
|
||||||
|
fetchAccessTokenMock.mockResolvedValue({ access_token: 'passport-token' })
|
||||||
|
|
||||||
|
render(<CheckCode />)
|
||||||
|
|
||||||
|
fireEvent.change(
|
||||||
|
screen.getByPlaceholderText('login.checkCode.verificationCodePlaceholder'),
|
||||||
|
{ target: { value: '123456' } },
|
||||||
|
)
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'login.checkCode.verify' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(fetchAccessTokenMock).toHaveBeenCalledWith({
|
||||||
|
appCode: 'test-app',
|
||||||
|
userId: 'embedded-user-99',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
expect(setWebAppAccessTokenMock).toHaveBeenCalledWith('code-token')
|
||||||
|
expect(setWebAppPassportMock).toHaveBeenCalledWith('test-app', 'passport-token')
|
||||||
|
expect(replaceMock).toHaveBeenCalledWith('/chatbot/test-app')
|
||||||
|
})
|
||||||
|
})
|
||||||
155
web/__tests__/embedded-user-id-store.test.tsx
Normal file
155
web/__tests__/embedded-user-id-store.test.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen, waitFor } from '@testing-library/react'
|
||||||
|
|
||||||
|
import WebAppStoreProvider, { useWebAppStore } from '@/context/web-app-context'
|
||||||
|
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
usePathname: jest.fn(() => '/chatbot/sample-app'),
|
||||||
|
useSearchParams: jest.fn(() => {
|
||||||
|
const params = new URLSearchParams()
|
||||||
|
return params
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
jest.mock('@/service/use-share', () => {
|
||||||
|
const { AccessMode } = jest.requireActual('@/models/access-control')
|
||||||
|
return {
|
||||||
|
useGetWebAppAccessModeByCode: jest.fn(() => ({
|
||||||
|
isLoading: false,
|
||||||
|
data: { accessMode: AccessMode.PUBLIC },
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
jest.mock('@/app/components/base/chat/utils', () => ({
|
||||||
|
getProcessedSystemVariablesFromUrlParams: jest.fn(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const { getProcessedSystemVariablesFromUrlParams: mockGetProcessedSystemVariablesFromUrlParams }
|
||||||
|
= jest.requireMock('@/app/components/base/chat/utils') as {
|
||||||
|
getProcessedSystemVariablesFromUrlParams: jest.Mock
|
||||||
|
}
|
||||||
|
|
||||||
|
jest.mock('@/context/global-public-context', () => {
|
||||||
|
const mockGlobalStoreState = {
|
||||||
|
isGlobalPending: false,
|
||||||
|
setIsGlobalPending: jest.fn(),
|
||||||
|
systemFeatures: {},
|
||||||
|
setSystemFeatures: jest.fn(),
|
||||||
|
}
|
||||||
|
const useGlobalPublicStore = Object.assign(
|
||||||
|
(selector?: (state: typeof mockGlobalStoreState) => any) =>
|
||||||
|
selector ? selector(mockGlobalStoreState) : mockGlobalStoreState,
|
||||||
|
{
|
||||||
|
setState: (updater: any) => {
|
||||||
|
if (typeof updater === 'function')
|
||||||
|
Object.assign(mockGlobalStoreState, updater(mockGlobalStoreState) ?? {})
|
||||||
|
|
||||||
|
else
|
||||||
|
Object.assign(mockGlobalStoreState, updater)
|
||||||
|
},
|
||||||
|
__mockState: mockGlobalStoreState,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
useGlobalPublicStore,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const {
|
||||||
|
useGlobalPublicStore: useGlobalPublicStoreMock,
|
||||||
|
} = jest.requireMock('@/context/global-public-context') as {
|
||||||
|
useGlobalPublicStore: ((selector?: (state: any) => any) => any) & {
|
||||||
|
setState: (updater: any) => void
|
||||||
|
__mockState: {
|
||||||
|
isGlobalPending: boolean
|
||||||
|
setIsGlobalPending: jest.Mock
|
||||||
|
systemFeatures: Record<string, unknown>
|
||||||
|
setSystemFeatures: jest.Mock
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mockGlobalStoreState = useGlobalPublicStoreMock.__mockState
|
||||||
|
|
||||||
|
const TestConsumer = () => {
|
||||||
|
const embeddedUserId = useWebAppStore(state => state.embeddedUserId)
|
||||||
|
const embeddedConversationId = useWebAppStore(state => state.embeddedConversationId)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div data-testid="embedded-user-id">{embeddedUserId ?? 'null'}</div>
|
||||||
|
<div data-testid="embedded-conversation-id">{embeddedConversationId ?? 'null'}</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialWebAppStore = (() => {
|
||||||
|
const snapshot = useWebAppStore.getState()
|
||||||
|
return {
|
||||||
|
shareCode: null as string | null,
|
||||||
|
appInfo: null,
|
||||||
|
appParams: null,
|
||||||
|
webAppAccessMode: snapshot.webAppAccessMode,
|
||||||
|
appMeta: null,
|
||||||
|
userCanAccessApp: false,
|
||||||
|
embeddedUserId: null,
|
||||||
|
embeddedConversationId: null,
|
||||||
|
updateShareCode: snapshot.updateShareCode,
|
||||||
|
updateAppInfo: snapshot.updateAppInfo,
|
||||||
|
updateAppParams: snapshot.updateAppParams,
|
||||||
|
updateWebAppAccessMode: snapshot.updateWebAppAccessMode,
|
||||||
|
updateWebAppMeta: snapshot.updateWebAppMeta,
|
||||||
|
updateUserCanAccessApp: snapshot.updateUserCanAccessApp,
|
||||||
|
updateEmbeddedUserId: snapshot.updateEmbeddedUserId,
|
||||||
|
updateEmbeddedConversationId: snapshot.updateEmbeddedConversationId,
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockGlobalStoreState.isGlobalPending = false
|
||||||
|
mockGetProcessedSystemVariablesFromUrlParams.mockReset()
|
||||||
|
useWebAppStore.setState(initialWebAppStore, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('WebAppStoreProvider embedded user id handling', () => {
|
||||||
|
it('hydrates embedded user and conversation ids from system variables', async () => {
|
||||||
|
mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({
|
||||||
|
user_id: 'iframe-user-123',
|
||||||
|
conversation_id: 'conversation-456',
|
||||||
|
})
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WebAppStoreProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</WebAppStoreProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('embedded-user-id')).toHaveTextContent('iframe-user-123')
|
||||||
|
expect(screen.getByTestId('embedded-conversation-id')).toHaveTextContent('conversation-456')
|
||||||
|
})
|
||||||
|
expect(useWebAppStore.getState().embeddedUserId).toBe('iframe-user-123')
|
||||||
|
expect(useWebAppStore.getState().embeddedConversationId).toBe('conversation-456')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears embedded user id when system variable is absent', async () => {
|
||||||
|
useWebAppStore.setState(state => ({
|
||||||
|
...state,
|
||||||
|
embeddedUserId: 'previous-user',
|
||||||
|
embeddedConversationId: 'existing-conversation',
|
||||||
|
}))
|
||||||
|
mockGetProcessedSystemVariablesFromUrlParams.mockResolvedValue({})
|
||||||
|
|
||||||
|
render(
|
||||||
|
<WebAppStoreProvider>
|
||||||
|
<TestConsumer />
|
||||||
|
</WebAppStoreProvider>,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByTestId('embedded-user-id')).toHaveTextContent('null')
|
||||||
|
expect(screen.getByTestId('embedded-conversation-id')).toHaveTextContent('null')
|
||||||
|
})
|
||||||
|
expect(useWebAppStore.getState().embeddedUserId).toBeNull()
|
||||||
|
expect(useWebAppStore.getState().embeddedConversationId).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -15,6 +15,7 @@ const Splash: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const shareCode = useWebAppStore(s => s.shareCode)
|
const shareCode = useWebAppStore(s => s.shareCode)
|
||||||
const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
|
const webAppAccessMode = useWebAppStore(s => s.webAppAccessMode)
|
||||||
|
const embeddedUserId = useWebAppStore(s => s.embeddedUserId)
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const redirectUrl = searchParams.get('redirect_url')
|
const redirectUrl = searchParams.get('redirect_url')
|
||||||
@ -69,7 +70,10 @@ const Splash: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
}
|
}
|
||||||
else if (userLoggedIn && !appLoggedIn) {
|
else if (userLoggedIn && !appLoggedIn) {
|
||||||
try {
|
try {
|
||||||
const { access_token } = await fetchAccessToken({ appCode: shareCode! })
|
const { access_token } = await fetchAccessToken({
|
||||||
|
appCode: shareCode!,
|
||||||
|
userId: embeddedUserId || undefined,
|
||||||
|
})
|
||||||
setWebAppPassport(shareCode!, access_token)
|
setWebAppPassport(shareCode!, access_token)
|
||||||
redirectOrFinish()
|
redirectOrFinish()
|
||||||
}
|
}
|
||||||
@ -85,7 +89,8 @@ const Splash: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
router,
|
router,
|
||||||
message,
|
message,
|
||||||
webAppAccessMode,
|
webAppAccessMode,
|
||||||
tokenFromUrl])
|
tokenFromUrl,
|
||||||
|
embeddedUserId])
|
||||||
|
|
||||||
if (message) {
|
if (message) {
|
||||||
return <div className='flex h-full flex-col items-center justify-center gap-y-4'>
|
return <div className='flex h-full flex-col items-center justify-center gap-y-4'>
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { sendWebAppEMailLoginCode, webAppEmailLoginWithCode } from '@/service/co
|
|||||||
import I18NContext from '@/context/i18n'
|
import I18NContext from '@/context/i18n'
|
||||||
import { setWebAppAccessToken, setWebAppPassport } from '@/service/webapp-auth'
|
import { setWebAppAccessToken, setWebAppPassport } from '@/service/webapp-auth'
|
||||||
import { fetchAccessToken } from '@/service/share'
|
import { fetchAccessToken } from '@/service/share'
|
||||||
|
import { useWebAppStore } from '@/context/web-app-context'
|
||||||
|
|
||||||
export default function CheckCode() {
|
export default function CheckCode() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@ -23,6 +24,7 @@ export default function CheckCode() {
|
|||||||
const [loading, setIsLoading] = useState(false)
|
const [loading, setIsLoading] = useState(false)
|
||||||
const { locale } = useContext(I18NContext)
|
const { locale } = useContext(I18NContext)
|
||||||
const redirectUrl = searchParams.get('redirect_url')
|
const redirectUrl = searchParams.get('redirect_url')
|
||||||
|
const embeddedUserId = useWebAppStore(s => s.embeddedUserId)
|
||||||
|
|
||||||
const getAppCodeFromRedirectUrl = useCallback(() => {
|
const getAppCodeFromRedirectUrl = useCallback(() => {
|
||||||
if (!redirectUrl)
|
if (!redirectUrl)
|
||||||
@ -63,7 +65,10 @@ export default function CheckCode() {
|
|||||||
const ret = await webAppEmailLoginWithCode({ email, code, token })
|
const ret = await webAppEmailLoginWithCode({ email, code, token })
|
||||||
if (ret.result === 'success') {
|
if (ret.result === 'success') {
|
||||||
setWebAppAccessToken(ret.data.access_token)
|
setWebAppAccessToken(ret.data.access_token)
|
||||||
const { access_token } = await fetchAccessToken({ appCode: appCode! })
|
const { access_token } = await fetchAccessToken({
|
||||||
|
appCode: appCode!,
|
||||||
|
userId: embeddedUserId || undefined,
|
||||||
|
})
|
||||||
setWebAppPassport(appCode!, access_token)
|
setWebAppPassport(appCode!, access_token)
|
||||||
router.replace(decodeURIComponent(redirectUrl))
|
router.replace(decodeURIComponent(redirectUrl))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { emailRegex } from '@/config'
|
|||||||
import { webAppLogin } from '@/service/common'
|
import { webAppLogin } from '@/service/common'
|
||||||
import Input from '@/app/components/base/input'
|
import Input from '@/app/components/base/input'
|
||||||
import I18NContext from '@/context/i18n'
|
import I18NContext from '@/context/i18n'
|
||||||
|
import { useWebAppStore } from '@/context/web-app-context'
|
||||||
import { noop } from 'lodash-es'
|
import { noop } from 'lodash-es'
|
||||||
import { fetchAccessToken } from '@/service/share'
|
import { fetchAccessToken } from '@/service/share'
|
||||||
import { setWebAppAccessToken, setWebAppPassport } from '@/service/webapp-auth'
|
import { setWebAppAccessToken, setWebAppPassport } from '@/service/webapp-auth'
|
||||||
@ -30,6 +31,7 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const redirectUrl = searchParams.get('redirect_url')
|
const redirectUrl = searchParams.get('redirect_url')
|
||||||
|
const embeddedUserId = useWebAppStore(s => s.embeddedUserId)
|
||||||
|
|
||||||
const getAppCodeFromRedirectUrl = useCallback(() => {
|
const getAppCodeFromRedirectUrl = useCallback(() => {
|
||||||
if (!redirectUrl)
|
if (!redirectUrl)
|
||||||
@ -82,7 +84,10 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
|||||||
if (res.result === 'success') {
|
if (res.result === 'success') {
|
||||||
setWebAppAccessToken(res.data.access_token)
|
setWebAppAccessToken(res.data.access_token)
|
||||||
|
|
||||||
const { access_token } = await fetchAccessToken({ appCode: appCode! })
|
const { access_token } = await fetchAccessToken({
|
||||||
|
appCode: appCode!,
|
||||||
|
userId: embeddedUserId || undefined,
|
||||||
|
})
|
||||||
setWebAppPassport(appCode!, access_token)
|
setWebAppPassport(appCode!, access_token)
|
||||||
router.replace(decodeURIComponent(redirectUrl))
|
router.replace(decodeURIComponent(redirectUrl))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,16 +66,20 @@ export const useEmbeddedChatbot = () => {
|
|||||||
const appInfo = useWebAppStore(s => s.appInfo)
|
const appInfo = useWebAppStore(s => s.appInfo)
|
||||||
const appMeta = useWebAppStore(s => s.appMeta)
|
const appMeta = useWebAppStore(s => s.appMeta)
|
||||||
const appParams = useWebAppStore(s => s.appParams)
|
const appParams = useWebAppStore(s => s.appParams)
|
||||||
|
const embeddedConversationId = useWebAppStore(s => s.embeddedConversationId)
|
||||||
|
const embeddedUserId = useWebAppStore(s => s.embeddedUserId)
|
||||||
const appId = useMemo(() => appInfo?.app_id, [appInfo])
|
const appId = useMemo(() => appInfo?.app_id, [appInfo])
|
||||||
|
|
||||||
const [userId, setUserId] = useState<string>()
|
const [userId, setUserId] = useState<string>()
|
||||||
const [conversationId, setConversationId] = useState<string>()
|
const [conversationId, setConversationId] = useState<string>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getProcessedSystemVariablesFromUrlParams().then(({ user_id, conversation_id }) => {
|
setUserId(embeddedUserId || undefined)
|
||||||
setUserId(user_id)
|
}, [embeddedUserId])
|
||||||
setConversationId(conversation_id)
|
|
||||||
})
|
useEffect(() => {
|
||||||
}, [])
|
setConversationId(embeddedConversationId || undefined)
|
||||||
|
}, [embeddedConversationId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setLanguageFromParams = async () => {
|
const setLanguageFromParams = async () => {
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { usePathname, useSearchParams } from 'next/navigation'
|
|||||||
import type { FC, PropsWithChildren } from 'react'
|
import type { FC, PropsWithChildren } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
|
import { getProcessedSystemVariablesFromUrlParams } from '@/app/components/base/chat/utils'
|
||||||
import { useGlobalPublicStore } from './global-public-context'
|
import { useGlobalPublicStore } from './global-public-context'
|
||||||
|
|
||||||
type WebAppStore = {
|
type WebAppStore = {
|
||||||
@ -24,6 +25,10 @@ type WebAppStore = {
|
|||||||
updateWebAppMeta: (appMeta: AppMeta | null) => void
|
updateWebAppMeta: (appMeta: AppMeta | null) => void
|
||||||
userCanAccessApp: boolean
|
userCanAccessApp: boolean
|
||||||
updateUserCanAccessApp: (canAccess: boolean) => void
|
updateUserCanAccessApp: (canAccess: boolean) => void
|
||||||
|
embeddedUserId: string | null
|
||||||
|
updateEmbeddedUserId: (userId: string | null) => void
|
||||||
|
embeddedConversationId: string | null
|
||||||
|
updateEmbeddedConversationId: (conversationId: string | null) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useWebAppStore = create<WebAppStore>(set => ({
|
export const useWebAppStore = create<WebAppStore>(set => ({
|
||||||
@ -39,6 +44,11 @@ export const useWebAppStore = create<WebAppStore>(set => ({
|
|||||||
updateWebAppMeta: (appMeta: AppMeta | null) => set(() => ({ appMeta })),
|
updateWebAppMeta: (appMeta: AppMeta | null) => set(() => ({ appMeta })),
|
||||||
userCanAccessApp: false,
|
userCanAccessApp: false,
|
||||||
updateUserCanAccessApp: (canAccess: boolean) => set(() => ({ userCanAccessApp: canAccess })),
|
updateUserCanAccessApp: (canAccess: boolean) => set(() => ({ userCanAccessApp: canAccess })),
|
||||||
|
embeddedUserId: null,
|
||||||
|
updateEmbeddedUserId: (userId: string | null) => set(() => ({ embeddedUserId: userId })),
|
||||||
|
embeddedConversationId: null,
|
||||||
|
updateEmbeddedConversationId: (conversationId: string | null) =>
|
||||||
|
set(() => ({ embeddedConversationId: conversationId })),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const getShareCodeFromRedirectUrl = (redirectUrl: string | null): string | null => {
|
const getShareCodeFromRedirectUrl = (redirectUrl: string | null): string | null => {
|
||||||
@ -58,9 +68,12 @@ const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending)
|
const isGlobalPending = useGlobalPublicStore(s => s.isGlobalPending)
|
||||||
const updateWebAppAccessMode = useWebAppStore(state => state.updateWebAppAccessMode)
|
const updateWebAppAccessMode = useWebAppStore(state => state.updateWebAppAccessMode)
|
||||||
const updateShareCode = useWebAppStore(state => state.updateShareCode)
|
const updateShareCode = useWebAppStore(state => state.updateShareCode)
|
||||||
|
const updateEmbeddedUserId = useWebAppStore(state => state.updateEmbeddedUserId)
|
||||||
|
const updateEmbeddedConversationId = useWebAppStore(state => state.updateEmbeddedConversationId)
|
||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams()
|
||||||
const redirectUrlParam = searchParams.get('redirect_url')
|
const redirectUrlParam = searchParams.get('redirect_url')
|
||||||
|
const searchParamsString = searchParams.toString()
|
||||||
|
|
||||||
// Compute shareCode directly
|
// Compute shareCode directly
|
||||||
const shareCode = getShareCodeFromRedirectUrl(redirectUrlParam) || getShareCodeFromPathname(pathname)
|
const shareCode = getShareCodeFromRedirectUrl(redirectUrlParam) || getShareCodeFromPathname(pathname)
|
||||||
@ -68,6 +81,29 @@ const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
|
|||||||
updateShareCode(shareCode)
|
updateShareCode(shareCode)
|
||||||
}, [shareCode, updateShareCode])
|
}, [shareCode, updateShareCode])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false
|
||||||
|
const syncEmbeddedUserId = async () => {
|
||||||
|
try {
|
||||||
|
const { user_id, conversation_id } = await getProcessedSystemVariablesFromUrlParams()
|
||||||
|
if (!cancelled) {
|
||||||
|
updateEmbeddedUserId(user_id || null)
|
||||||
|
updateEmbeddedConversationId(conversation_id || null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
if (!cancelled) {
|
||||||
|
updateEmbeddedUserId(null)
|
||||||
|
updateEmbeddedConversationId(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
syncEmbeddedUserId()
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [searchParamsString, updateEmbeddedUserId, updateEmbeddedConversationId])
|
||||||
|
|
||||||
const { isLoading, data: accessModeResult } = useGetWebAppAccessModeByCode(shareCode)
|
const { isLoading, data: accessModeResult } = useGetWebAppAccessModeByCode(shareCode)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user