import React, {useState, useEffect, useRef} from 'react'; import * as Sentry from '@sentry/react-native'; import {Severity} from '@sentry/types'; import {StackActions} from '@react-navigation/native'; import jwt_decode from 'jwt-decode'; import {AxiosResponse} from 'axios'; import BackgroundTimer from 'react-native-background-timer'; import Clipboard from '@react-native-community/clipboard'; import {useRoute} from '@react-navigation/native'; import {AffinidiWalletV6} from '@affinidi/wallet-react-native-sdk'; import moment from 'moment'; import OTPInputView from '@twotalltotems/react-native-otp-input'; import {useNavigation} from '@react-navigation/native'; import {SdkService} from '@services/AffinidiSDKService'; import ErrorModal from '@components/ErrorModal'; import Button from '@components/Button'; import useUserContext from '@contexts/user'; import useLoaderContext from '@contexts/loader'; import { issuanceServiceInstance, cloudWalletServiceInstance, } from '@services/Axios'; import {LOGIN} from '@constants/navigation/onboarding'; import {config} from '@constants/config'; import { CONFIRM_SIGN_IN_PASSWORD_LESS, SIGN_IN_PASSWORD_LESS, REGISTER_USER, TEST_USER_NAME, TEST_USER_MOBILE_NUMBER, TEST_USER_PASSWORD, } from '@constants/api'; import { Container, Title, PhoneNumber, changePhoneNumberButtonStyle, otpInputViewStyle, codeInputFieldStyle, codeInputHighlightStyle, textButtonStyles, Timer, } from '@styles/screens/otp'; const timer = 60; const OtpScreen = () => { const route = useRoute(); const otpInputElement = useRef(null); const params: any = route?.params; const {showLoader, hideLoader} = useLoaderContext(); const { getLoginToken, setAccessToken, setTokenExpiresIn, setPassword, setEncryptedSeed, getPhoneNumber, setLoginToken, } = useUserContext(); const navigation = useNavigation(); const [error, setError] = useState<{ message: string; type: 'success' | 'error'; } | null>(); const [seconds, setSeconds] = useState(timer); const [phoneNumber, setPhoneNumber] = useState(undefined); const [code, setCode] = useState(); const [resendOtpCount, setResendOtpCount] = useState(0); let [otpCounter, setOtpCounter] = useState(0); const getWallet = async ( value: string, ): Promise<{ wallet?: import('@affinidi/wallet-core-sdk/dist/CommonNetworkMember/NetworkMemberWithCognito').NetworkMemberWithCognito; error?: string; }> => { const logInToken = await getLoginToken(); const isTestUser = phoneNumber === TEST_USER_MOBILE_NUMBER; if (isTestUser) { const response = await SdkService.logInWithPassword( TEST_USER_NAME, TEST_USER_PASSWORD, ); return response; } else if (logInToken && value) { try { // const response = await SdkService.completeSignInPasswordless( // logInToken, // value, // ); // return response; const response: AxiosResponse = await cloudWalletServiceInstance.post( CONFIRM_SIGN_IN_PASSWORD_LESS, {token: logInToken, confirmationCode: value}, ); const serializedSession = JSON.stringify({ accessToken: response?.data?.accessToken, }); const wallet = await AffinidiWalletV6.deserializeSession( config, serializedSession, ); return {wallet}; } catch (e: any) { return { wallet: undefined, error: e?.message === 'Request failed with status code 400' ? 'Confirmation code is invalid' : e?.message, }; } } return {wallet: undefined, error: 'Something went wrong'}; }; const verifyOtp = async (value: string) => { try { if (value) { setOtpCounter(++otpCounter); showLoader(); const {error: sdkError, wallet} = await getWallet(value); hideLoader(); if (sdkError) { let message = sdkError === 'Invalid operation parameters.' ? 'Confirmation code is invalid' : sdkError; if (otpCounter === 3) { message = `You entered ${otpCounter} times wrong OTP, Please try after sometime.`; setTimeout(() => { navigation.dispatch(StackActions.replace(LOGIN?.SCREEN)); }, 2000); } setError({ message, type: 'error', }); Sentry.captureMessage( `Error : ${message}, Mobile Number : [${phoneNumber}]`, Severity.Error, ); } else { const cognitoUserTokens = wallet?.cognitoUserTokens; const accessToken: string | undefined = cognitoUserTokens?.accessToken; if (accessToken) { const tokenDecrypt: any = jwt_decode(accessToken); if (tokenDecrypt && tokenDecrypt?.exp) { await setTokenExpiresIn(tokenDecrypt?.exp); } if (wallet?._password) { await setPassword(wallet?._password); } if (wallet?._encryptedSeed) { await setEncryptedSeed(wallet?._encryptedSeed); } if (params?.isSignUpScreen && !!accessToken) { Sentry.captureMessage( `Calling /user/register api ${phoneNumber} ${accessToken}`, Severity.Log, ); try { await issuanceServiceInstance.post( REGISTER_USER, { name: 'emptyString', userId: phoneNumber, consented: 'yes', tandCAcceptedDate: moment().format(), }, { headers: { Authorization: accessToken, 'content-type': 'application/json', }, }, ); } catch (e: any) { setError({message: e?.message, type: 'error'}); Sentry.captureMessage( `Error while calling /user/register api ${phoneNumber} ${e.message} ${accessToken}`, Severity.Error, ); return; } } setError({message: 'OTP successfully verified', type: 'success'}); setTimeout(async () => { Clipboard.setString(''); setError(null); if (!!cognitoUserTokens && !!accessToken) { await setAccessToken(accessToken); } }, 1000); } else { Sentry.captureMessage( `Access token is not available : ${phoneNumber}`, Severity.Error, ); } } } } catch (e: any) { Sentry.captureMessage( `Error : ${e?.message}, Mobile Number : [${phoneNumber}]`, Severity.Error, ); } }; useEffect(() => { const myInterval = BackgroundTimer.setInterval(() => { if (seconds > 0) { setSeconds(seconds - 1); } if (seconds === 0) { BackgroundTimer.clearInterval(myInterval); } }, 1000); return () => { BackgroundTimer.clearInterval(myInterval); }; }, [seconds]); useEffect(() => { setTimeout(() => { otpInputElement?.current?.focusField(0); }, 0); }, []); useEffect(() => { (async () => { const value = await getPhoneNumber(); if (value) { setPhoneNumber(value); } })(); }, [getPhoneNumber]); return ( Enter OTP sent to {phoneNumber}