1+ import { sendErrorResponse } from '@animo-id/expo-digital-credentials-api'
12import { type OnWalletAuthSubmitProps , WalletFlowAuthPrompt } from '@easypid/components/WalletFlowAuthPrompt'
23import { paradymWalletSdkOptions } from '@easypid/config/paradym'
34import { setupWalletServiceProvider , setWalletServiceProviderPin } from '@easypid/crypto/WalletServiceProviderClient'
45import { useShouldUseCloudHsm } from '@easypid/features/onboarding/useShouldUseCloudHsm'
6+ import { useDevelopmentMode } from '@easypid/hooks'
57import type { SubmissionAuthorizationMode } from '@easypid/hooks/useSubmissionAuthorizationMode'
68import {
79 authorizeWalletFlow ,
810 clearWalletFlowAuthorization ,
911 isWalletAuthPromptError ,
1012} from '@easypid/utils/authorizeWalletFlow'
11- import { TranslationProvider } from '@package/translations'
13+ import { useLingui } from '@lingui/react/macro'
14+ import { commonMessages , TranslationProvider } from '@package/translations'
1215import { Stack , TamaguiProvider , YStack } from '@package/ui'
1316import { type DigitalCredentialsRequest , ParadymWalletSdk , useParadym } from '@paradym/wallet-sdk'
1417import { useEffect , useRef , useState } from 'react'
1518import { SafeAreaProvider , useSafeAreaInsets } from 'react-native-safe-area-context'
1619import tamaguiConfig from '../../../tamagui.config'
1720import { useStoredLocale } from '../../hooks/useStoredLocale'
21+ import { InteractionErrorSlide } from '../receive/slides/InteractionErrorSlide'
1822
1923type DcApiSharingScreenProps = {
2024 request : DigitalCredentialsRequest
@@ -39,11 +43,15 @@ export function DcApiSharingScreen({ request }: DcApiSharingScreenProps) {
3943}
4044
4145export function DcApiSharingScreenWithContext ( { request } : DcApiSharingScreenProps ) {
46+ const { t } = useLingui ( )
4247 const [ isProcessing , setIsProcessing ] = useState ( false )
48+ const [ errorReason , setErrorReason ] = useState < string > ( )
4349 const cloudHsmPinRef = useRef < string | undefined > ( undefined )
4450 const onAuthorizationErrorRef = useRef < ( ( ) => void ) | undefined > ( undefined )
51+ const errorResponseMessageRef = useRef ( 'Unable to share credentials' )
4552 const insets = useSafeAreaInsets ( )
4653 const paradym = useParadym ( )
54+ const [ isDevelopmentModeEnabled ] = useDevelopmentMode ( )
4755 const [ shouldUseCloudHsmValue ] = useShouldUseCloudHsm ( )
4856 const shouldUseCloudHsm = shouldUseCloudHsmValue === true
4957 const authorizationMode : Exclude < SubmissionAuthorizationMode , 'none' > = shouldUseCloudHsm
@@ -52,40 +60,62 @@ export function DcApiSharingScreenWithContext({ request }: DcApiSharingScreenPro
5260 const isAuthorizing =
5361 isProcessing || paradym . state === 'acquired-wallet-key' || ( paradym . state === 'locked' && paradym . isUnlocking )
5462
63+ const setFlowError = ( {
64+ reason,
65+ error,
66+ responseMessage,
67+ } : {
68+ reason : string
69+ error : unknown
70+ responseMessage : string
71+ } ) => {
72+ errorResponseMessageRef . current = responseMessage
73+ const errorMessage =
74+ error instanceof Error && isDevelopmentModeEnabled ? `Development mode error: ${ error . message } ` : undefined
75+
76+ setErrorReason ( errorMessage ? `${ reason } \n${ errorMessage } ` : reason )
77+ }
78+
5579 const onShareResponse = async ( sdk : ParadymWalletSdk ) => {
56- const resolvedRequest = await sdk . dcApi
57- . resolveRequest ( { request } )
58- . then ( ( resolvedRequest ) => {
59- // We can't share multiple documents at the moment
60- if ( resolvedRequest . formattedSubmission . entries . length > 1 ) {
61- throw new Error ( 'Multiple cards requested, but only one card can be shared with the digital credentials api.' )
62- }
80+ let resolvedRequest : Awaited < ReturnType < typeof sdk . dcApi . resolveRequest > >
6381
64- return resolvedRequest
65- } )
66- . catch ( ( error ) => {
67- sdk . logger . error ( 'Error getting credentials for dc api request' , {
68- error,
69- } )
82+ try {
83+ resolvedRequest = await sdk . dcApi . resolveRequest ( { request } )
7084
71- // Not shown to the user
72- sdk . dcApi . sendErrorResponse ( 'Presentation information could not be extracted' )
85+ // We can't share multiple documents at the moment
86+ if ( resolvedRequest . formattedSubmission . entries . length > 1 ) {
87+ throw new Error ( 'Multiple cards requested, but only one card can be shared with the digital credentials api.' )
88+ }
89+ } catch ( error ) {
90+ sdk . logger . error ( 'Error getting credentials for dc api request' , {
91+ error,
7392 } )
7493
75- if ( ! resolvedRequest ) return
94+ setFlowError ( {
95+ reason : t ( commonMessages . presentationInformationCouldNotBeExtracted ) ,
96+ error,
97+ responseMessage : 'Presentation information could not be extracted' ,
98+ } )
99+ return false
100+ }
76101
77102 // Once this returns we just assume it's successful
78103 try {
79104 await sdk . dcApi . sendResponse ( {
80105 dcRequest : request ,
81106 resolvedRequest,
82107 } )
108+
109+ return true
83110 } catch ( error ) {
84111 sdk . logger . error ( 'Could not share response' , { error } )
85112
86- // Not shown to the user
87- sdk . dcApi . sendErrorResponse ( 'Unable to share credentials' )
88- return
113+ setFlowError ( {
114+ reason : t ( commonMessages . presentationCouldNotBeShared ) ,
115+ error,
116+ responseMessage : 'Unable to share credentials' ,
117+ } )
118+ return false
89119 }
90120 }
91121
@@ -110,7 +140,11 @@ export function DcApiSharingScreenWithContext({ request }: DcApiSharingScreenPro
110140 return
111141 }
112142
113- throw error
143+ setFlowError ( {
144+ reason : t ( commonMessages . presentationCouldNotBeShared ) ,
145+ error,
146+ responseMessage : 'Unable to share credentials' ,
147+ } )
114148 } )
115149 . finally ( ( ) => {
116150 cloudHsmPinRef . current = undefined
@@ -123,25 +157,25 @@ export function DcApiSharingScreenWithContext({ request }: DcApiSharingScreenPro
123157 const onAuthorize = async ( { pin, onAuthorized, onAuthorizationError } : OnWalletAuthSubmitProps = { } ) => {
124158 onAuthorizationErrorRef . current = onAuthorizationError
125159
126- if ( paradym . state === 'locked' ) {
127- if ( shouldUseCloudHsm ) {
128- if ( ! pin ) throw new Error ( 'PIN is required to use Cloud HSM' )
129- cloudHsmPinRef . current = pin
130- }
160+ try {
161+ if ( paradym . state === 'locked' ) {
162+ if ( shouldUseCloudHsm ) {
163+ if ( ! pin ) throw new Error ( 'PIN is required to use Cloud HSM' )
164+ cloudHsmPinRef . current = pin
165+ }
131166
132- if ( pin ) {
133- await paradym . unlockUsingPin ( pin )
134- } else {
135- await paradym . tryUnlockingUsingBiometrics ( )
136- }
167+ if ( pin ) {
168+ await paradym . unlockUsingPin ( pin )
169+ } else {
170+ await paradym . tryUnlockingUsingBiometrics ( )
171+ }
137172
138- onAuthorized ?.( )
139- return
140- }
173+ onAuthorized ?.( )
174+ return
175+ }
141176
142- if ( paradym . state === 'unlocked' ) {
143- setIsProcessing ( true )
144- try {
177+ if ( paradym . state === 'unlocked' ) {
178+ setIsProcessing ( true )
145179 await authorizeWalletFlow ( {
146180 mode : authorizationMode ,
147181 pin,
@@ -151,23 +185,27 @@ export function DcApiSharingScreenWithContext({ request }: DcApiSharingScreenPro
151185 await setupWalletServiceProvider ( paradym . paradym )
152186 }
153187
154- await onShareResponse ( paradym . paradym )
155- onAuthorized ?.( )
156- } catch ( error ) {
157- if ( isWalletAuthPromptError ( error ) ) {
158- onAuthorizationError ?.( )
159- return
160- }
188+ const didShare = await onShareResponse ( paradym . paradym )
189+ if ( didShare ) onAuthorized ?.( )
190+ return
191+ }
161192
162- throw error
163- } finally {
164- clearWalletFlowAuthorization ( )
165- setIsProcessing ( false )
193+ throw new Error ( `Invalid state. Received: '${ paradym . state } '` )
194+ } catch ( error ) {
195+ if ( isWalletAuthPromptError ( error ) ) {
196+ onAuthorizationError ?.( )
197+ return
166198 }
167- return
168- }
169199
170- throw new Error ( `Invalid state. Received: '${ paradym . state } '` )
200+ setFlowError ( {
201+ reason : t ( commonMessages . presentationCouldNotBeShared ) ,
202+ error,
203+ responseMessage : 'Unable to share credentials' ,
204+ } )
205+ } finally {
206+ clearWalletFlowAuthorization ( )
207+ setIsProcessing ( false )
208+ }
171209 }
172210
173211 return (
@@ -179,14 +217,24 @@ export function DcApiSharingScreenWithContext({ request }: DcApiSharingScreenPro
179217 p = "$4"
180218 paddingBottom = { insets . bottom ?? '$6' }
181219 >
182- < Stack pt = "$5" >
183- < WalletFlowAuthPrompt
184- authMode = { authorizationMode }
185- onSubmit = { onAuthorize }
186- isLoading = { isAuthorizing }
187- annotation = { request . origin }
220+ { errorReason ? (
221+ < InteractionErrorSlide
222+ flowType = "verify"
223+ reason = { errorReason }
224+ layout = "content"
225+ buttonLabel = { t ( commonMessages . close ) }
226+ onCancel = { ( ) => sendErrorResponse ( { errorMessage : errorResponseMessageRef . current } ) }
188227 />
189- </ Stack >
228+ ) : (
229+ < Stack pt = "$5" >
230+ < WalletFlowAuthPrompt
231+ authMode = { authorizationMode }
232+ onSubmit = { onAuthorize }
233+ isLoading = { isAuthorizing }
234+ annotation = { request . origin }
235+ />
236+ </ Stack >
237+ ) }
190238 </ YStack >
191239 )
192240}
0 commit comments