@hexar/biometric-identity-sdk-react-native 1.0.15 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/BiometricIdentityFlow.d.ts.map +1 -1
- package/dist/components/BiometricIdentityFlow.js +54 -17
- package/dist/components/VideoRecorder.js +1 -1
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -1
- package/dist/hooks/useBiometricSDK.js +41 -4
- package/package.json +1 -1
- package/src/components/BiometricIdentityFlow.tsx +66 -24
- package/src/components/VideoRecorder.tsx +1 -1
- package/src/hooks/useBiometricSDK.ts +46 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AAU5C,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CA8VtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
|
|
@@ -55,6 +55,15 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
55
55
|
const [showInstructions, setShowInstructions] = (0, react_1.useState)(true);
|
|
56
56
|
const [currentChallenges, setCurrentChallenges] = (0, react_1.useState)([]);
|
|
57
57
|
const [isLoadingChallenges, setIsLoadingChallenges] = (0, react_1.useState)(false);
|
|
58
|
+
// Use refs to store callbacks to avoid dependency issues in useEffect
|
|
59
|
+
const onValidationCompleteRef = (0, react_1.useRef)(onValidationComplete);
|
|
60
|
+
const onErrorRef = (0, react_1.useRef)(onError);
|
|
61
|
+
const hasCalledValidationComplete = (0, react_1.useRef)(false);
|
|
62
|
+
// Update refs when callbacks change
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
onValidationCompleteRef.current = onValidationComplete;
|
|
65
|
+
onErrorRef.current = onError;
|
|
66
|
+
}, [onValidationComplete, onError]);
|
|
58
67
|
// Set language early, before any components render
|
|
59
68
|
// Priority: language prop > SDK config language > default 'en'
|
|
60
69
|
// Run on mount and whenever language prop changes
|
|
@@ -71,17 +80,27 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
71
80
|
const styles = createStyles(theme);
|
|
72
81
|
// Handle validation result - call callback when RESULT step is reached
|
|
73
82
|
(0, react_1.useEffect)(() => {
|
|
74
|
-
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
84
|
+
biometric_identity_sdk_core_1.logger.info('Validation completed, calling onValidationComplete callback');
|
|
85
|
+
hasCalledValidationComplete.current = true;
|
|
86
|
+
// Use ref to avoid dependency issues
|
|
87
|
+
onValidationCompleteRef.current(state.validationResult);
|
|
88
|
+
}
|
|
89
|
+
}, [state.currentStep, state.validationResult]);
|
|
90
|
+
// Reset the flag when validation result changes or component resets
|
|
91
|
+
(0, react_1.useEffect)(() => {
|
|
92
|
+
if (state.currentStep !== biometric_identity_sdk_core_1.SDKStep.RESULT) {
|
|
93
|
+
hasCalledValidationComplete.current = false;
|
|
77
94
|
}
|
|
78
|
-
}, [state.currentStep
|
|
95
|
+
}, [state.currentStep]);
|
|
79
96
|
// Handle error
|
|
80
97
|
(0, react_1.useEffect)(() => {
|
|
81
98
|
if (state.error) {
|
|
82
|
-
|
|
99
|
+
biometric_identity_sdk_core_1.logger.error('SDK error detected:', state.error);
|
|
100
|
+
// Use ref to avoid dependency issues
|
|
101
|
+
onErrorRef.current(state.error);
|
|
83
102
|
}
|
|
84
|
-
}, [state.error
|
|
103
|
+
}, [state.error]);
|
|
85
104
|
/**
|
|
86
105
|
* Start capture process
|
|
87
106
|
*/
|
|
@@ -129,22 +148,35 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
129
148
|
sessionId: videoResult.sessionId,
|
|
130
149
|
});
|
|
131
150
|
biometric_identity_sdk_core_1.logger.info('Starting validation...');
|
|
132
|
-
await validateIdentity();
|
|
151
|
+
const result = await validateIdentity();
|
|
152
|
+
biometric_identity_sdk_core_1.logger.info('Validation completed successfully:', result);
|
|
153
|
+
// Ensure state is synced after validation
|
|
154
|
+
// The state should be updated by validateIdentity, but we'll force a sync
|
|
155
|
+
const finalState = sdk.getState();
|
|
156
|
+
biometric_identity_sdk_core_1.logger.info('Final state after validation:', finalState);
|
|
157
|
+
// Backup: If we have a result but the useEffect didn't fire, call the callback directly
|
|
158
|
+
if (finalState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
159
|
+
biometric_identity_sdk_core_1.logger.info('Backup: Calling onValidationComplete directly after validation');
|
|
160
|
+
hasCalledValidationComplete.current = true;
|
|
161
|
+
onValidationCompleteRef.current(finalState.validationResult);
|
|
162
|
+
}
|
|
133
163
|
}
|
|
134
164
|
}
|
|
135
165
|
catch (error) {
|
|
136
|
-
biometric_identity_sdk_core_1.logger.error('Capture error:', error);
|
|
137
|
-
|
|
166
|
+
biometric_identity_sdk_core_1.logger.error('Capture/validation error:', error);
|
|
167
|
+
const biometricError = {
|
|
138
168
|
name: 'BiometricError',
|
|
139
169
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
140
170
|
code: 'LIVENESS_CHECK_FAILED',
|
|
141
|
-
}
|
|
171
|
+
};
|
|
172
|
+
onErrorRef.current(biometricError);
|
|
142
173
|
}
|
|
143
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
174
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
144
175
|
/**
|
|
145
176
|
* Handle retry
|
|
146
177
|
*/
|
|
147
178
|
const handleRetry = (0, react_1.useCallback)(() => {
|
|
179
|
+
hasCalledValidationComplete.current = false;
|
|
148
180
|
reset();
|
|
149
181
|
setCurrentChallenges([]);
|
|
150
182
|
setShowInstructions(true);
|
|
@@ -170,17 +202,22 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
170
202
|
}
|
|
171
203
|
return (react_1.default.createElement(CameraCapture_1.CameraCapture, { mode: cameraMode, theme: theme, language: language, onCapture: handleCaptureComplete, onCancel: () => setShowCamera(false) }));
|
|
172
204
|
}
|
|
173
|
-
// Show
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
205
|
+
// Show result (check this first, even if step is still VALIDATING but result exists)
|
|
206
|
+
// This handles edge cases where state transition might be delayed
|
|
207
|
+
if (state.validationResult && (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || state.progress >= 100)) {
|
|
208
|
+
// If we have a result but step is still VALIDATING, force transition to RESULT
|
|
209
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING && state.progress >= 100) {
|
|
210
|
+
biometric_identity_sdk_core_1.logger.info('Result available but step still VALIDATING, showing ResultScreen');
|
|
211
|
+
}
|
|
179
212
|
return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => {
|
|
180
213
|
// Callback already called automatically when RESULT step was reached
|
|
181
214
|
// This onClose is just for UI cleanup/navigation
|
|
182
215
|
} }));
|
|
183
216
|
}
|
|
217
|
+
// Show validation progress
|
|
218
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
|
|
219
|
+
return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
|
|
220
|
+
}
|
|
184
221
|
// Show error
|
|
185
222
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR && state.error) {
|
|
186
223
|
return (react_1.default.createElement(ErrorScreen_1.ErrorScreen, { error: state.error, theme: theme, language: language, onRetry: handleRetry, onClose: () => onError(state.error) }));
|
|
@@ -84,7 +84,7 @@ const getInstructionMap = (strings) => ({
|
|
|
84
84
|
smile: { text: strings.liveness.instructions.smile || 'Smile 😊', icon: '😊' },
|
|
85
85
|
blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally', icon: '👁' },
|
|
86
86
|
open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly', icon: '😮' },
|
|
87
|
-
stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and
|
|
87
|
+
stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions', icon: '📷' },
|
|
88
88
|
});
|
|
89
89
|
const VideoRecorder = ({ theme, language, duration, instructions, challenges: propChallenges, sessionId, smartMode = true, onComplete, onCancel, onFetchChallenges, }) => {
|
|
90
90
|
if (language) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBiometricSDK.d.ts","sourceRoot":"","sources":["../../src/hooks/useBiometricSDK.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,WAAW,
|
|
1
|
+
{"version":3,"file":"useBiometricSDK.d.ts","sourceRoot":"","sources":["../../src/hooks/useBiometricSDK.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,WAAW,EAGZ,MAAM,oCAAoC,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,oBAAoB,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,mBAAmB,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC7E,gBAAgB,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,WAAW,EAAE,CAAC,SAAS,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,eAAO,MAAM,eAAe,QAAO,qBAgQlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -137,24 +137,61 @@ const useBiometricSDK = () => {
|
|
|
137
137
|
* Validate identity with all collected data
|
|
138
138
|
*/
|
|
139
139
|
const validateIdentity = (0, react_1.useCallback)(async () => {
|
|
140
|
+
biometric_identity_sdk_core_1.logger.info('validateIdentity called, current state:', sdk.getState());
|
|
140
141
|
setState(sdk.getState());
|
|
141
142
|
const pollInterval = setInterval(() => {
|
|
142
143
|
if (isMounted.current) {
|
|
143
|
-
|
|
144
|
+
const currentState = sdk.getState();
|
|
145
|
+
setState(currentState);
|
|
144
146
|
}
|
|
145
147
|
}, 200);
|
|
146
148
|
try {
|
|
147
149
|
const result = await sdk.validateIdentity();
|
|
148
|
-
|
|
150
|
+
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
151
|
+
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
152
|
+
let pollCount = 0;
|
|
153
|
+
const maxPolls = 10; // Poll for up to 2 seconds (10 * 200ms)
|
|
154
|
+
const finalPollInterval = setInterval(() => {
|
|
155
|
+
pollCount++;
|
|
156
|
+
if (isMounted.current) {
|
|
157
|
+
const currentState = sdk.getState();
|
|
158
|
+
biometric_identity_sdk_core_1.logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
159
|
+
setState(currentState);
|
|
160
|
+
// Stop polling once we reach RESULT or ERROR state
|
|
161
|
+
if (currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
162
|
+
clearInterval(finalPollInterval);
|
|
163
|
+
clearInterval(pollInterval);
|
|
164
|
+
biometric_identity_sdk_core_1.logger.info('Reached final state, stopped polling:', currentState);
|
|
165
|
+
}
|
|
166
|
+
else if (pollCount >= maxPolls) {
|
|
167
|
+
// Force stop after max polls
|
|
168
|
+
clearInterval(finalPollInterval);
|
|
169
|
+
clearInterval(pollInterval);
|
|
170
|
+
const finalState = sdk.getState();
|
|
171
|
+
biometric_identity_sdk_core_1.logger.warn('Max polls reached, forcing final state:', finalState);
|
|
172
|
+
setState(finalState);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}, 200);
|
|
176
|
+
// Also get immediate state after promise resolves
|
|
149
177
|
if (isMounted.current) {
|
|
150
|
-
|
|
178
|
+
const immediateState = sdk.getState();
|
|
179
|
+
biometric_identity_sdk_core_1.logger.info('Validation result received, immediate state:', immediateState);
|
|
180
|
+
setState(immediateState);
|
|
181
|
+
// If we're already at RESULT, clear intervals immediately
|
|
182
|
+
if (immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
183
|
+
clearInterval(finalPollInterval);
|
|
184
|
+
clearInterval(pollInterval);
|
|
185
|
+
}
|
|
151
186
|
}
|
|
152
187
|
return result;
|
|
153
188
|
}
|
|
154
189
|
catch (error) {
|
|
155
190
|
clearInterval(pollInterval);
|
|
156
191
|
if (isMounted.current) {
|
|
157
|
-
|
|
192
|
+
const errorState = sdk.getState();
|
|
193
|
+
biometric_identity_sdk_core_1.logger.error('Validation error, state:', errorState, error);
|
|
194
|
+
setState(errorState);
|
|
158
195
|
}
|
|
159
196
|
throw error;
|
|
160
197
|
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Main UI component for identity verification with backend AI support
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import React, { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
7
7
|
import {
|
|
8
8
|
View,
|
|
9
9
|
Text,
|
|
@@ -76,6 +76,17 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
76
76
|
const [showInstructions, setShowInstructions] = useState(true);
|
|
77
77
|
const [currentChallenges, setCurrentChallenges] = useState<ChallengeAction[]>([]);
|
|
78
78
|
const [isLoadingChallenges, setIsLoadingChallenges] = useState(false);
|
|
79
|
+
|
|
80
|
+
// Use refs to store callbacks to avoid dependency issues in useEffect
|
|
81
|
+
const onValidationCompleteRef = useRef(onValidationComplete);
|
|
82
|
+
const onErrorRef = useRef(onError);
|
|
83
|
+
const hasCalledValidationComplete = useRef(false);
|
|
84
|
+
|
|
85
|
+
// Update refs when callbacks change
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
onValidationCompleteRef.current = onValidationComplete;
|
|
88
|
+
onErrorRef.current = onError;
|
|
89
|
+
}, [onValidationComplete, onError]);
|
|
79
90
|
|
|
80
91
|
// Set language early, before any components render
|
|
81
92
|
// Priority: language prop > SDK config language > default 'en'
|
|
@@ -95,18 +106,29 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
95
106
|
|
|
96
107
|
// Handle validation result - call callback when RESULT step is reached
|
|
97
108
|
useEffect(() => {
|
|
98
|
-
if (state.currentStep === SDKStep.RESULT && state.validationResult) {
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
if (state.currentStep === SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
110
|
+
logger.info('Validation completed, calling onValidationComplete callback');
|
|
111
|
+
hasCalledValidationComplete.current = true;
|
|
112
|
+
// Use ref to avoid dependency issues
|
|
113
|
+
onValidationCompleteRef.current(state.validationResult);
|
|
114
|
+
}
|
|
115
|
+
}, [state.currentStep, state.validationResult]);
|
|
116
|
+
|
|
117
|
+
// Reset the flag when validation result changes or component resets
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (state.currentStep !== SDKStep.RESULT) {
|
|
120
|
+
hasCalledValidationComplete.current = false;
|
|
101
121
|
}
|
|
102
|
-
}, [state.currentStep
|
|
122
|
+
}, [state.currentStep]);
|
|
103
123
|
|
|
104
124
|
// Handle error
|
|
105
125
|
useEffect(() => {
|
|
106
126
|
if (state.error) {
|
|
107
|
-
|
|
127
|
+
logger.error('SDK error detected:', state.error);
|
|
128
|
+
// Use ref to avoid dependency issues
|
|
129
|
+
onErrorRef.current(state.error);
|
|
108
130
|
}
|
|
109
|
-
}, [state.error
|
|
131
|
+
}, [state.error]);
|
|
110
132
|
|
|
111
133
|
/**
|
|
112
134
|
* Start capture process
|
|
@@ -157,22 +179,37 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
157
179
|
});
|
|
158
180
|
|
|
159
181
|
logger.info('Starting validation...');
|
|
160
|
-
await validateIdentity();
|
|
182
|
+
const result = await validateIdentity();
|
|
183
|
+
logger.info('Validation completed successfully:', result);
|
|
184
|
+
|
|
185
|
+
// Ensure state is synced after validation
|
|
186
|
+
// The state should be updated by validateIdentity, but we'll force a sync
|
|
187
|
+
const finalState = sdk.getState();
|
|
188
|
+
logger.info('Final state after validation:', finalState);
|
|
189
|
+
|
|
190
|
+
// Backup: If we have a result but the useEffect didn't fire, call the callback directly
|
|
191
|
+
if (finalState.currentStep === SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
192
|
+
logger.info('Backup: Calling onValidationComplete directly after validation');
|
|
193
|
+
hasCalledValidationComplete.current = true;
|
|
194
|
+
onValidationCompleteRef.current(finalState.validationResult);
|
|
195
|
+
}
|
|
161
196
|
}
|
|
162
197
|
} catch (error) {
|
|
163
|
-
logger.error('Capture error:', error);
|
|
164
|
-
|
|
198
|
+
logger.error('Capture/validation error:', error);
|
|
199
|
+
const biometricError: BiometricError = {
|
|
165
200
|
name: 'BiometricError',
|
|
166
201
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
167
202
|
code: 'LIVENESS_CHECK_FAILED',
|
|
168
|
-
} as BiometricError
|
|
203
|
+
} as BiometricError;
|
|
204
|
+
onErrorRef.current(biometricError);
|
|
169
205
|
}
|
|
170
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
206
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
171
207
|
|
|
172
208
|
/**
|
|
173
209
|
* Handle retry
|
|
174
210
|
*/
|
|
175
211
|
const handleRetry = useCallback(() => {
|
|
212
|
+
hasCalledValidationComplete.current = false;
|
|
176
213
|
reset();
|
|
177
214
|
setCurrentChallenges([]);
|
|
178
215
|
setShowInstructions(true);
|
|
@@ -234,28 +271,33 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
234
271
|
);
|
|
235
272
|
}
|
|
236
273
|
|
|
237
|
-
// Show
|
|
238
|
-
|
|
274
|
+
// Show result (check this first, even if step is still VALIDATING but result exists)
|
|
275
|
+
// This handles edge cases where state transition might be delayed
|
|
276
|
+
if (state.validationResult && (state.currentStep === SDKStep.RESULT || state.progress >= 100)) {
|
|
277
|
+
// If we have a result but step is still VALIDATING, force transition to RESULT
|
|
278
|
+
if (state.currentStep === SDKStep.VALIDATING && state.progress >= 100) {
|
|
279
|
+
logger.info('Result available but step still VALIDATING, showing ResultScreen');
|
|
280
|
+
}
|
|
239
281
|
return (
|
|
240
|
-
<
|
|
241
|
-
|
|
282
|
+
<ResultScreen
|
|
283
|
+
result={state.validationResult}
|
|
242
284
|
theme={theme}
|
|
243
285
|
language={language}
|
|
286
|
+
onClose={() => {
|
|
287
|
+
// Callback already called automatically when RESULT step was reached
|
|
288
|
+
// This onClose is just for UI cleanup/navigation
|
|
289
|
+
}}
|
|
244
290
|
/>
|
|
245
291
|
);
|
|
246
292
|
}
|
|
247
293
|
|
|
248
|
-
// Show
|
|
249
|
-
if (state.currentStep === SDKStep.
|
|
294
|
+
// Show validation progress
|
|
295
|
+
if (state.currentStep === SDKStep.VALIDATING) {
|
|
250
296
|
return (
|
|
251
|
-
<
|
|
252
|
-
|
|
297
|
+
<ValidationProgress
|
|
298
|
+
progress={state.progress}
|
|
253
299
|
theme={theme}
|
|
254
300
|
language={language}
|
|
255
|
-
onClose={() => {
|
|
256
|
-
// Callback already called automatically when RESULT step was reached
|
|
257
|
-
// This onClose is just for UI cleanup/navigation
|
|
258
|
-
}}
|
|
259
301
|
/>
|
|
260
302
|
);
|
|
261
303
|
}
|
|
@@ -99,7 +99,7 @@ const getInstructionMap = (strings: any): Record<string, { text: string; icon: s
|
|
|
99
99
|
smile: { text: strings.liveness.instructions.smile || 'Smile 😊', icon: '😊' },
|
|
100
100
|
blink: { text: strings.liveness.instructions.blink || 'Blink your eyes naturally', icon: '👁' },
|
|
101
101
|
open_mouth: { text: strings.liveness.instructions.openMouth || 'Open your mouth slightly', icon: '😮' },
|
|
102
|
-
stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and
|
|
102
|
+
stay_still: { text: strings.liveness.instructions.stayStill || 'Look at the camera and follow the instructions', icon: '📷' },
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
BiometricIdentitySDK,
|
|
4
4
|
SDKState,
|
|
5
5
|
VideoResult,
|
|
6
|
+
SDKStep,
|
|
6
7
|
logger,
|
|
7
8
|
} from '@hexar/biometric-identity-sdk-core';
|
|
8
9
|
|
|
@@ -193,26 +194,67 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
193
194
|
* Validate identity with all collected data
|
|
194
195
|
*/
|
|
195
196
|
const validateIdentity = useCallback(async () => {
|
|
197
|
+
logger.info('validateIdentity called, current state:', sdk.getState());
|
|
196
198
|
setState(sdk.getState());
|
|
197
199
|
|
|
198
200
|
const pollInterval = setInterval(() => {
|
|
199
201
|
if (isMounted.current) {
|
|
200
|
-
|
|
202
|
+
const currentState = sdk.getState();
|
|
203
|
+
setState(currentState);
|
|
201
204
|
}
|
|
202
205
|
}, 200);
|
|
203
206
|
|
|
204
207
|
try {
|
|
205
208
|
const result = await sdk.validateIdentity();
|
|
206
|
-
clearInterval(pollInterval);
|
|
207
209
|
|
|
210
|
+
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
211
|
+
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
212
|
+
let pollCount = 0;
|
|
213
|
+
const maxPolls = 10; // Poll for up to 2 seconds (10 * 200ms)
|
|
214
|
+
|
|
215
|
+
const finalPollInterval = setInterval(() => {
|
|
216
|
+
pollCount++;
|
|
217
|
+
if (isMounted.current) {
|
|
218
|
+
const currentState = sdk.getState();
|
|
219
|
+
logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
220
|
+
setState(currentState);
|
|
221
|
+
|
|
222
|
+
// Stop polling once we reach RESULT or ERROR state
|
|
223
|
+
if (currentState.currentStep === SDKStep.RESULT || currentState.currentStep === SDKStep.ERROR) {
|
|
224
|
+
clearInterval(finalPollInterval);
|
|
225
|
+
clearInterval(pollInterval);
|
|
226
|
+
logger.info('Reached final state, stopped polling:', currentState);
|
|
227
|
+
} else if (pollCount >= maxPolls) {
|
|
228
|
+
// Force stop after max polls
|
|
229
|
+
clearInterval(finalPollInterval);
|
|
230
|
+
clearInterval(pollInterval);
|
|
231
|
+
const finalState = sdk.getState();
|
|
232
|
+
logger.warn('Max polls reached, forcing final state:', finalState);
|
|
233
|
+
setState(finalState);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}, 200);
|
|
237
|
+
|
|
238
|
+
// Also get immediate state after promise resolves
|
|
208
239
|
if (isMounted.current) {
|
|
209
|
-
|
|
240
|
+
const immediateState = sdk.getState();
|
|
241
|
+
logger.info('Validation result received, immediate state:', immediateState);
|
|
242
|
+
setState(immediateState);
|
|
243
|
+
|
|
244
|
+
// If we're already at RESULT, clear intervals immediately
|
|
245
|
+
if (immediateState.currentStep === SDKStep.RESULT || immediateState.currentStep === SDKStep.ERROR) {
|
|
246
|
+
clearInterval(finalPollInterval);
|
|
247
|
+
clearInterval(pollInterval);
|
|
248
|
+
}
|
|
210
249
|
}
|
|
250
|
+
|
|
211
251
|
return result;
|
|
212
252
|
} catch (error) {
|
|
213
253
|
clearInterval(pollInterval);
|
|
214
254
|
if (isMounted.current) {
|
|
215
|
-
|
|
255
|
+
const errorState = sdk.getState();
|
|
256
|
+
logger.error('Validation error, state:', errorState, error);
|
|
257
|
+
setState(errorState);
|
|
216
258
|
}
|
|
217
259
|
throw error;
|
|
218
260
|
}
|