import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, } from 'react' import {Dimensions, View} from 'react-native' import * as Linking from 'expo-linking' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {retry} from '#/lib/async/retry' import {wait} from '#/lib/async/wait' import {parseLinkingUrl} from '#/lib/parseLinkingUrl' import {useAgent, useSession} from '#/state/session' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonText} from '#/components/Button' import {FullWindowOverlay} from '#/components/FullWindowOverlay' import {CheckThick_Stroke2_Corner0_Rounded as SuccessIcon} from '#/components/icons/Check' import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {refetchAgeAssuranceServerState} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {IS_IOS, IS_WEB} from '#/env' export type RedirectOverlayState = { result: 'success' | 'unknown' actorDid: string } /** * Validate and parse the query parameters returned from the age assurance * redirect. If not valid, returns `undefined` and the dialog will not open. */ export function parseRedirectOverlayState( state: { result?: string actorDid?: string } = {}, ): RedirectOverlayState | undefined { let result: RedirectOverlayState['result'] = 'unknown' const actorDid = state.actorDid switch (state.result) { case 'success': result = 'success' break case 'unknown': default: result = 'unknown' break } if (actorDid) { return { result, actorDid, } } } const Context = createContext<{ isOpen: boolean open: (state: RedirectOverlayState) => void close: () => void }>({ isOpen: false, open: () => {}, close: () => {}, }) export function useRedirectOverlayContext() { return useContext(Context) } export function Provider({children}: {children?: React.ReactNode}) { const {currentAccount} = useSession() const incomingUrl = Linking.useLinkingURL() const [state, setState] = useState(() => { if (!incomingUrl) return null const url = parseLinkingUrl(incomingUrl) if (url.pathname !== '/intent/age-assurance') return null const params = url.searchParams const state = parseRedirectOverlayState({ result: params.get('result') ?? undefined, actorDid: params.get('actorDid') ?? undefined, }) if (IS_WEB) { // Clear the URL parameters so they don't re-trigger history.pushState(null, '', '/') } /* * If we don't have an account or the account doesn't match, do * nothing. By the time the user switches to their other account, AA * state should be ready for them. */ if (state && currentAccount && state.actorDid === currentAccount.did) { return state } return null }) const open = useCallback((state: RedirectOverlayState) => { setState(state) }, []) const close = useCallback(() => { setState(null) }, []) return ( ({ isOpen: state !== null, open, close, }), [state, open, close], )}> {children} ) } export function RedirectOverlay() { const t = useTheme() const {_} = useLingui() const {isOpen} = useRedirectOverlayContext() const {gtMobile} = useBreakpoints() return isOpen ? ( ) : null } function Inner() { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() const agent = useAgent() const polling = useRef(false) const unmounted = useRef(false) const [error, setError] = useState(false) const [success, setSuccess] = useState(false) const {close} = useRedirectOverlayContext() useEffect(() => { if (polling.current) return polling.current = true ax.metric('ageAssurance:redirectDialogOpen', {}) wait( 3e3, retry( 5, () => true, async () => { if (!agent.session) return if (unmounted.current) return const data = await refetchAgeAssuranceServerState({agent}) if (data?.state.status !== 'assured') { throw new Error( `Polling for age assurance state did not receive assured status`, ) } return data }, 1e3, ), ) .then(async data => { if (!data) return if (!agent.session) return if (unmounted.current) return setSuccess(true) ax.metric('ageAssurance:redirectDialogSuccess', {}) }) .catch(() => { if (unmounted.current) return setError(true) ax.metric('ageAssurance:redirectDialogFail', {}) }) return () => { unmounted.current = true } }, [ax, agent]) if (success) { return ( <> Success We've confirmed your age assurance status. You can now close this dialog. ) } return ( <> {error && } {error ? Connection issue : Verifying} {!error && } {error ? ( We were unable to receive the verification due to a connection issue. It may arrive later. If it does, your account will update automatically. ) : ( We're confirming your age assurance status with our servers. This should only take a few seconds. )} {error && ( )} ) }