@hexar/biometric-identity-sdk-react-native 1.0.14 → 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 +56 -15
- 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 +67 -21
- 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
|
|
@@ -69,18 +78,29 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
69
78
|
}, [language]);
|
|
70
79
|
const strings = (0, biometric_identity_sdk_core_1.getStrings)();
|
|
71
80
|
const styles = createStyles(theme);
|
|
72
|
-
// Handle validation result
|
|
81
|
+
// Handle validation result - call callback when RESULT step is reached
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
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
|
|
73
91
|
(0, react_1.useEffect)(() => {
|
|
74
|
-
if (state.
|
|
75
|
-
|
|
92
|
+
if (state.currentStep !== biometric_identity_sdk_core_1.SDKStep.RESULT) {
|
|
93
|
+
hasCalledValidationComplete.current = false;
|
|
76
94
|
}
|
|
77
|
-
}, [state.
|
|
95
|
+
}, [state.currentStep]);
|
|
78
96
|
// Handle error
|
|
79
97
|
(0, react_1.useEffect)(() => {
|
|
80
98
|
if (state.error) {
|
|
81
|
-
|
|
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);
|
|
82
102
|
}
|
|
83
|
-
}, [state.error
|
|
103
|
+
}, [state.error]);
|
|
84
104
|
/**
|
|
85
105
|
* Start capture process
|
|
86
106
|
*/
|
|
@@ -128,22 +148,35 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
128
148
|
sessionId: videoResult.sessionId,
|
|
129
149
|
});
|
|
130
150
|
biometric_identity_sdk_core_1.logger.info('Starting validation...');
|
|
131
|
-
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
|
+
}
|
|
132
163
|
}
|
|
133
164
|
}
|
|
134
165
|
catch (error) {
|
|
135
|
-
biometric_identity_sdk_core_1.logger.error('Capture error:', error);
|
|
136
|
-
|
|
166
|
+
biometric_identity_sdk_core_1.logger.error('Capture/validation error:', error);
|
|
167
|
+
const biometricError = {
|
|
137
168
|
name: 'BiometricError',
|
|
138
169
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
139
170
|
code: 'LIVENESS_CHECK_FAILED',
|
|
140
|
-
}
|
|
171
|
+
};
|
|
172
|
+
onErrorRef.current(biometricError);
|
|
141
173
|
}
|
|
142
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
174
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
143
175
|
/**
|
|
144
176
|
* Handle retry
|
|
145
177
|
*/
|
|
146
178
|
const handleRetry = (0, react_1.useCallback)(() => {
|
|
179
|
+
hasCalledValidationComplete.current = false;
|
|
147
180
|
reset();
|
|
148
181
|
setCurrentChallenges([]);
|
|
149
182
|
setShowInstructions(true);
|
|
@@ -169,14 +202,22 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
169
202
|
}
|
|
170
203
|
return (react_1.default.createElement(CameraCapture_1.CameraCapture, { mode: cameraMode, theme: theme, language: language, onCapture: handleCaptureComplete, onCancel: () => setShowCamera(false) }));
|
|
171
204
|
}
|
|
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
|
+
}
|
|
212
|
+
return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => {
|
|
213
|
+
// Callback already called automatically when RESULT step was reached
|
|
214
|
+
// This onClose is just for UI cleanup/navigation
|
|
215
|
+
} }));
|
|
216
|
+
}
|
|
172
217
|
// Show validation progress
|
|
173
218
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
|
|
174
219
|
return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
|
|
175
220
|
}
|
|
176
|
-
// Show result
|
|
177
|
-
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
|
|
178
|
-
return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => onValidationComplete(state.validationResult) }));
|
|
179
|
-
}
|
|
180
221
|
// Show error
|
|
181
222
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR && state.error) {
|
|
182
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'
|
|
@@ -93,19 +104,31 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
93
104
|
const strings = getStrings();
|
|
94
105
|
const styles = createStyles(theme);
|
|
95
106
|
|
|
96
|
-
// Handle validation result
|
|
107
|
+
// Handle validation result - call callback when RESULT step is reached
|
|
97
108
|
useEffect(() => {
|
|
98
|
-
if (state.validationResult) {
|
|
99
|
-
onValidationComplete
|
|
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);
|
|
100
114
|
}
|
|
101
|
-
}, [state.
|
|
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;
|
|
121
|
+
}
|
|
122
|
+
}, [state.currentStep]);
|
|
102
123
|
|
|
103
124
|
// Handle error
|
|
104
125
|
useEffect(() => {
|
|
105
126
|
if (state.error) {
|
|
106
|
-
|
|
127
|
+
logger.error('SDK error detected:', state.error);
|
|
128
|
+
// Use ref to avoid dependency issues
|
|
129
|
+
onErrorRef.current(state.error);
|
|
107
130
|
}
|
|
108
|
-
}, [state.error
|
|
131
|
+
}, [state.error]);
|
|
109
132
|
|
|
110
133
|
/**
|
|
111
134
|
* Start capture process
|
|
@@ -156,22 +179,37 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
156
179
|
});
|
|
157
180
|
|
|
158
181
|
logger.info('Starting validation...');
|
|
159
|
-
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
|
+
}
|
|
160
196
|
}
|
|
161
197
|
} catch (error) {
|
|
162
|
-
logger.error('Capture error:', error);
|
|
163
|
-
|
|
198
|
+
logger.error('Capture/validation error:', error);
|
|
199
|
+
const biometricError: BiometricError = {
|
|
164
200
|
name: 'BiometricError',
|
|
165
201
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
166
202
|
code: 'LIVENESS_CHECK_FAILED',
|
|
167
|
-
} as BiometricError
|
|
203
|
+
} as BiometricError;
|
|
204
|
+
onErrorRef.current(biometricError);
|
|
168
205
|
}
|
|
169
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
206
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
170
207
|
|
|
171
208
|
/**
|
|
172
209
|
* Handle retry
|
|
173
210
|
*/
|
|
174
211
|
const handleRetry = useCallback(() => {
|
|
212
|
+
hasCalledValidationComplete.current = false;
|
|
175
213
|
reset();
|
|
176
214
|
setCurrentChallenges([]);
|
|
177
215
|
setShowInstructions(true);
|
|
@@ -233,25 +271,33 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
233
271
|
);
|
|
234
272
|
}
|
|
235
273
|
|
|
236
|
-
// Show
|
|
237
|
-
|
|
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
|
+
}
|
|
238
281
|
return (
|
|
239
|
-
<
|
|
240
|
-
|
|
282
|
+
<ResultScreen
|
|
283
|
+
result={state.validationResult}
|
|
241
284
|
theme={theme}
|
|
242
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
|
+
}}
|
|
243
290
|
/>
|
|
244
291
|
);
|
|
245
292
|
}
|
|
246
293
|
|
|
247
|
-
// Show
|
|
248
|
-
if (state.currentStep === SDKStep.
|
|
294
|
+
// Show validation progress
|
|
295
|
+
if (state.currentStep === SDKStep.VALIDATING) {
|
|
249
296
|
return (
|
|
250
|
-
<
|
|
251
|
-
|
|
297
|
+
<ValidationProgress
|
|
298
|
+
progress={state.progress}
|
|
252
299
|
theme={theme}
|
|
253
300
|
language={language}
|
|
254
|
-
onClose={() => onValidationComplete(state.validationResult!)}
|
|
255
301
|
/>
|
|
256
302
|
);
|
|
257
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
|
}
|