import {useQuery, useQueryClient} from '@tanstack/react-query' import {type PropsWithChildren, useEffect, useRef, useState} from 'react' import {login, validateToken} from '@/api/auth.ts' import {userAssets} from '@/api/user.ts' import {useTranslation} from 'react-i18next' import {normalizeLanguage} from '@/lib/i18n' import {queryKeys} from '@/lib/queryKeys.ts' import {useUserStore} from '@/store/user.ts' import type {HostContextMessage} from '@/types' const HOST_READY_MESSAGE = 'PLAYX_READY' const HOST_READY_INTERVAL = 500 const HOST_READY_RETRY_LIMIT = 20 const TEST_BOOTSTRAP_ENABLED = import.meta.env.VITE_BYPASS_IFRAME_CONTEXT === 'true' const TEST_BOOTSTRAP_USERNAME = '+60777777777' const TEST_BOOTSTRAP_LANGUAGE = 'zh' export function AuthGuide({children}: PropsWithChildren) { const {t} = useTranslation() const queryClient = useQueryClient() const [username, setUsername] = useState(TEST_BOOTSTRAP_ENABLED ? TEST_BOOTSTRAP_USERNAME : '') const setUserInfo = useUserStore((state) => state.setUserInfo) const setAuthInfo = useUserStore((state) => state.setAuthInfo) const setAssetsInfo = useUserStore((state) => state.setAssetsInfo) const setLanguage = useUserStore((state) => state.setLanguage) const clearUserInfo = useUserStore((state) => state.clearUserInfo) const hasHostContextRef = useRef(false) const activeUsernameRef = useRef('') useEffect(() => { if (TEST_BOOTSTRAP_ENABLED) { hasHostContextRef.current = true activeUsernameRef.current = TEST_BOOTSTRAP_USERNAME setLanguage(TEST_BOOTSTRAP_LANGUAGE) return } const isEmbedded = window.parent !== window const notifyParentReady = () => { if (!isEmbedded || hasHostContextRef.current) { return } window.parent.postMessage({type: HOST_READY_MESSAGE}, '*') } const handleMessage = (event: MessageEvent) => { if (isEmbedded && event.source !== window.parent) { return } const message = event.data if (!message || message.type !== 'IFRAME_CONTEXT' || !message.payload) { return } const {language, username: nextUsername} = message.payload if (typeof nextUsername === 'string' && nextUsername.trim()) { const normalizedUsername = nextUsername.trim() if (activeUsernameRef.current && activeUsernameRef.current !== normalizedUsername) { clearUserInfo() queryClient.removeQueries({queryKey: ['auth-bootstrap']}) } hasHostContextRef.current = true activeUsernameRef.current = normalizedUsername setUsername(normalizedUsername) } if (typeof language === 'string' && language.trim()) { setLanguage(normalizeLanguage(language)) } } window.addEventListener('message', handleMessage) notifyParentReady() const retryTimer = isEmbedded ? window.setInterval(() => { notifyParentReady() }, HOST_READY_INTERVAL) : null const stopRetryTimer = isEmbedded ? window.setTimeout(() => { if (retryTimer != null) { window.clearInterval(retryTimer) } }, HOST_READY_INTERVAL * HOST_READY_RETRY_LIMIT) : null return () => { window.removeEventListener('message', handleMessage) if (retryTimer != null) { window.clearInterval(retryTimer) } if (stopRetryTimer != null) { window.clearTimeout(stopRetryTimer) } } }, [clearUserInfo, queryClient, setLanguage]) const authBootstrapQuery = useQuery({ queryKey: queryKeys.authBootstrap(username), enabled: Boolean(username), queryFn: async () => { const loginResponse = await login({username}) const userInfo = loginResponse.data.userInfo const validateResponse = await validateToken(userInfo.token) const authInfo = validateResponse.data const assetsResponse = await userAssets({ session_id: authInfo.session_id, }) return { userInfo, authInfo, assetsInfo: assetsResponse.data, } }, }) useEffect(() => { if (!authBootstrapQuery.data) { return } setUserInfo(authBootstrapQuery.data.userInfo) setAuthInfo(authBootstrapQuery.data.authInfo) setAssetsInfo(authBootstrapQuery.data.assetsInfo) queryClient.setQueryData(queryKeys.assets(authBootstrapQuery.data.authInfo.session_id), authBootstrapQuery.data.assetsInfo) }, [authBootstrapQuery.data, queryClient, setAssetsInfo, setAuthInfo, setUserInfo]) useEffect(() => { if (!authBootstrapQuery.isError) { return } clearUserInfo() }, [authBootstrapQuery.isError, clearUserInfo]) if (!username || authBootstrapQuery.isPending) { return (
{!username && !TEST_BOOTSTRAP_ENABLED ? t('auth.waitingForHostContext') : t('auth.loadingAccountData')}
) } if (authBootstrapQuery.isError) { return (
{t('auth.authenticationFailed')}
{t('auth.refreshAndTryAgain')}
) } return <>{children} }