@hexar/biometric-identity-sdk-react-native 1.0.15 → 1.0.17
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 +65 -17
- package/dist/components/VideoRecorder.js +1 -1
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -1
- package/dist/hooks/useBiometricSDK.js +45 -4
- package/package.json +1 -1
- package/src/components/BiometricIdentityFlow.tsx +77 -24
- package/src/components/VideoRecorder.tsx +1 -1
- package/src/hooks/useBiometricSDK.ts +50 -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,CAyWtE,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,29 @@ 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
|
-
|
|
75
|
-
|
|
76
|
-
onValidationComplete
|
|
83
|
+
console.log('🔵 [BiometricIdentityFlow] useEffect triggered - currentStep:', state.currentStep, 'hasResult:', !!state.validationResult, 'hasCalled:', hasCalledValidationComplete.current);
|
|
84
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
85
|
+
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete callback');
|
|
86
|
+
biometric_identity_sdk_core_1.logger.info('Validation completed, calling onValidationComplete callback');
|
|
87
|
+
hasCalledValidationComplete.current = true;
|
|
88
|
+
// Use ref to avoid dependency issues
|
|
89
|
+
onValidationCompleteRef.current(state.validationResult);
|
|
90
|
+
}
|
|
91
|
+
}, [state.currentStep, state.validationResult]);
|
|
92
|
+
// Reset the flag when validation result changes or component resets
|
|
93
|
+
(0, react_1.useEffect)(() => {
|
|
94
|
+
if (state.currentStep !== biometric_identity_sdk_core_1.SDKStep.RESULT) {
|
|
95
|
+
hasCalledValidationComplete.current = false;
|
|
77
96
|
}
|
|
78
|
-
}, [state.currentStep
|
|
97
|
+
}, [state.currentStep]);
|
|
79
98
|
// Handle error
|
|
80
99
|
(0, react_1.useEffect)(() => {
|
|
81
100
|
if (state.error) {
|
|
82
|
-
|
|
101
|
+
biometric_identity_sdk_core_1.logger.error('SDK error detected:', state.error);
|
|
102
|
+
// Use ref to avoid dependency issues
|
|
103
|
+
onErrorRef.current(state.error);
|
|
83
104
|
}
|
|
84
|
-
}, [state.error
|
|
105
|
+
}, [state.error]);
|
|
85
106
|
/**
|
|
86
107
|
* Start capture process
|
|
87
108
|
*/
|
|
@@ -128,23 +149,45 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
128
149
|
challengesCompleted: videoResult.challengesCompleted,
|
|
129
150
|
sessionId: videoResult.sessionId,
|
|
130
151
|
});
|
|
152
|
+
console.log('🔵 [BiometricIdentityFlow] Video stored, starting validation...');
|
|
131
153
|
biometric_identity_sdk_core_1.logger.info('Starting validation...');
|
|
132
|
-
await validateIdentity();
|
|
154
|
+
const result = await validateIdentity();
|
|
155
|
+
console.log('🟢 [BiometricIdentityFlow] Validation completed:', result);
|
|
156
|
+
biometric_identity_sdk_core_1.logger.info('Validation completed successfully:', result);
|
|
157
|
+
// Ensure state is synced after validation
|
|
158
|
+
// The state should be updated by validateIdentity, but we'll force a sync
|
|
159
|
+
const finalState = sdk.getState();
|
|
160
|
+
console.log('🔵 [BiometricIdentityFlow] Final state after validation:', JSON.stringify({
|
|
161
|
+
currentStep: finalState.currentStep,
|
|
162
|
+
hasResult: !!finalState.validationResult,
|
|
163
|
+
progress: finalState.progress,
|
|
164
|
+
isLoading: finalState.isLoading
|
|
165
|
+
}));
|
|
166
|
+
biometric_identity_sdk_core_1.logger.info('Final state after validation:', finalState);
|
|
167
|
+
// Backup: If we have a result but the useEffect didn't fire, call the callback directly
|
|
168
|
+
if (finalState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
169
|
+
console.log('🟢 [BiometricIdentityFlow] Backup: Calling onValidationComplete directly');
|
|
170
|
+
biometric_identity_sdk_core_1.logger.info('Backup: Calling onValidationComplete directly after validation');
|
|
171
|
+
hasCalledValidationComplete.current = true;
|
|
172
|
+
onValidationCompleteRef.current(finalState.validationResult);
|
|
173
|
+
}
|
|
133
174
|
}
|
|
134
175
|
}
|
|
135
176
|
catch (error) {
|
|
136
|
-
biometric_identity_sdk_core_1.logger.error('Capture error:', error);
|
|
137
|
-
|
|
177
|
+
biometric_identity_sdk_core_1.logger.error('Capture/validation error:', error);
|
|
178
|
+
const biometricError = {
|
|
138
179
|
name: 'BiometricError',
|
|
139
180
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
140
181
|
code: 'LIVENESS_CHECK_FAILED',
|
|
141
|
-
}
|
|
182
|
+
};
|
|
183
|
+
onErrorRef.current(biometricError);
|
|
142
184
|
}
|
|
143
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
185
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
144
186
|
/**
|
|
145
187
|
* Handle retry
|
|
146
188
|
*/
|
|
147
189
|
const handleRetry = (0, react_1.useCallback)(() => {
|
|
190
|
+
hasCalledValidationComplete.current = false;
|
|
148
191
|
reset();
|
|
149
192
|
setCurrentChallenges([]);
|
|
150
193
|
setShowInstructions(true);
|
|
@@ -170,17 +213,22 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
170
213
|
}
|
|
171
214
|
return (react_1.default.createElement(CameraCapture_1.CameraCapture, { mode: cameraMode, theme: theme, language: language, onCapture: handleCaptureComplete, onCancel: () => setShowCamera(false) }));
|
|
172
215
|
}
|
|
173
|
-
// Show
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
216
|
+
// Show result (check this first, even if step is still VALIDATING but result exists)
|
|
217
|
+
// This handles edge cases where state transition might be delayed
|
|
218
|
+
if (state.validationResult && (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || state.progress >= 100)) {
|
|
219
|
+
// If we have a result but step is still VALIDATING, force transition to RESULT
|
|
220
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING && state.progress >= 100) {
|
|
221
|
+
biometric_identity_sdk_core_1.logger.info('Result available but step still VALIDATING, showing ResultScreen');
|
|
222
|
+
}
|
|
179
223
|
return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => {
|
|
180
224
|
// Callback already called automatically when RESULT step was reached
|
|
181
225
|
// This onClose is just for UI cleanup/navigation
|
|
182
226
|
} }));
|
|
183
227
|
}
|
|
228
|
+
// Show validation progress
|
|
229
|
+
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
|
|
230
|
+
return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
|
|
231
|
+
}
|
|
184
232
|
// Show error
|
|
185
233
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR && state.error) {
|
|
186
234
|
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,qBAoQlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -137,24 +137,65 @@ const useBiometricSDK = () => {
|
|
|
137
137
|
* Validate identity with all collected data
|
|
138
138
|
*/
|
|
139
139
|
const validateIdentity = (0, react_1.useCallback)(async () => {
|
|
140
|
+
console.log('🔵 [useBiometricSDK] validateIdentity called');
|
|
141
|
+
biometric_identity_sdk_core_1.logger.info('validateIdentity called, current state:', sdk.getState());
|
|
140
142
|
setState(sdk.getState());
|
|
141
143
|
const pollInterval = setInterval(() => {
|
|
142
144
|
if (isMounted.current) {
|
|
143
|
-
|
|
145
|
+
const currentState = sdk.getState();
|
|
146
|
+
setState(currentState);
|
|
144
147
|
}
|
|
145
148
|
}, 200);
|
|
146
149
|
try {
|
|
150
|
+
console.log('🔵 [useBiometricSDK] Calling sdk.validateIdentity()...');
|
|
147
151
|
const result = await sdk.validateIdentity();
|
|
148
|
-
|
|
152
|
+
console.log('🟢 [useBiometricSDK] sdk.validateIdentity() completed, result:', result);
|
|
153
|
+
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
154
|
+
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
155
|
+
let pollCount = 0;
|
|
156
|
+
const maxPolls = 10; // Poll for up to 2 seconds (10 * 200ms)
|
|
157
|
+
const finalPollInterval = setInterval(() => {
|
|
158
|
+
pollCount++;
|
|
159
|
+
if (isMounted.current) {
|
|
160
|
+
const currentState = sdk.getState();
|
|
161
|
+
biometric_identity_sdk_core_1.logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
162
|
+
setState(currentState);
|
|
163
|
+
// Stop polling once we reach RESULT or ERROR state
|
|
164
|
+
if (currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
165
|
+
console.log('🟢 [useBiometricSDK] Reached final state:', currentState.currentStep);
|
|
166
|
+
clearInterval(finalPollInterval);
|
|
167
|
+
clearInterval(pollInterval);
|
|
168
|
+
biometric_identity_sdk_core_1.logger.info('Reached final state, stopped polling:', currentState);
|
|
169
|
+
}
|
|
170
|
+
else if (pollCount >= maxPolls) {
|
|
171
|
+
// Force stop after max polls
|
|
172
|
+
clearInterval(finalPollInterval);
|
|
173
|
+
clearInterval(pollInterval);
|
|
174
|
+
const finalState = sdk.getState();
|
|
175
|
+
biometric_identity_sdk_core_1.logger.warn('Max polls reached, forcing final state:', finalState);
|
|
176
|
+
setState(finalState);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}, 200);
|
|
180
|
+
// Also get immediate state after promise resolves
|
|
149
181
|
if (isMounted.current) {
|
|
150
|
-
|
|
182
|
+
const immediateState = sdk.getState();
|
|
183
|
+
biometric_identity_sdk_core_1.logger.info('Validation result received, immediate state:', immediateState);
|
|
184
|
+
setState(immediateState);
|
|
185
|
+
// If we're already at RESULT, clear intervals immediately
|
|
186
|
+
if (immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
187
|
+
clearInterval(finalPollInterval);
|
|
188
|
+
clearInterval(pollInterval);
|
|
189
|
+
}
|
|
151
190
|
}
|
|
152
191
|
return result;
|
|
153
192
|
}
|
|
154
193
|
catch (error) {
|
|
155
194
|
clearInterval(pollInterval);
|
|
156
195
|
if (isMounted.current) {
|
|
157
|
-
|
|
196
|
+
const errorState = sdk.getState();
|
|
197
|
+
biometric_identity_sdk_core_1.logger.error('Validation error, state:', errorState, error);
|
|
198
|
+
setState(errorState);
|
|
158
199
|
}
|
|
159
200
|
throw error;
|
|
160
201
|
}
|
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,31 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
95
106
|
|
|
96
107
|
// Handle validation result - call callback when RESULT step is reached
|
|
97
108
|
useEffect(() => {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
onValidationComplete
|
|
109
|
+
console.log('🔵 [BiometricIdentityFlow] useEffect triggered - currentStep:', state.currentStep, 'hasResult:', !!state.validationResult, 'hasCalled:', hasCalledValidationComplete.current);
|
|
110
|
+
if (state.currentStep === SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
111
|
+
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete callback');
|
|
112
|
+
logger.info('Validation completed, calling onValidationComplete callback');
|
|
113
|
+
hasCalledValidationComplete.current = true;
|
|
114
|
+
// Use ref to avoid dependency issues
|
|
115
|
+
onValidationCompleteRef.current(state.validationResult);
|
|
116
|
+
}
|
|
117
|
+
}, [state.currentStep, state.validationResult]);
|
|
118
|
+
|
|
119
|
+
// Reset the flag when validation result changes or component resets
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (state.currentStep !== SDKStep.RESULT) {
|
|
122
|
+
hasCalledValidationComplete.current = false;
|
|
101
123
|
}
|
|
102
|
-
}, [state.currentStep
|
|
124
|
+
}, [state.currentStep]);
|
|
103
125
|
|
|
104
126
|
// Handle error
|
|
105
127
|
useEffect(() => {
|
|
106
128
|
if (state.error) {
|
|
107
|
-
|
|
129
|
+
logger.error('SDK error detected:', state.error);
|
|
130
|
+
// Use ref to avoid dependency issues
|
|
131
|
+
onErrorRef.current(state.error);
|
|
108
132
|
}
|
|
109
|
-
}, [state.error
|
|
133
|
+
}, [state.error]);
|
|
110
134
|
|
|
111
135
|
/**
|
|
112
136
|
* Start capture process
|
|
@@ -156,23 +180,47 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
156
180
|
sessionId: videoResult.sessionId,
|
|
157
181
|
});
|
|
158
182
|
|
|
183
|
+
console.log('🔵 [BiometricIdentityFlow] Video stored, starting validation...');
|
|
159
184
|
logger.info('Starting validation...');
|
|
160
|
-
await validateIdentity();
|
|
185
|
+
const result = await validateIdentity();
|
|
186
|
+
console.log('🟢 [BiometricIdentityFlow] Validation completed:', result);
|
|
187
|
+
logger.info('Validation completed successfully:', result);
|
|
188
|
+
|
|
189
|
+
// Ensure state is synced after validation
|
|
190
|
+
// The state should be updated by validateIdentity, but we'll force a sync
|
|
191
|
+
const finalState = sdk.getState();
|
|
192
|
+
console.log('🔵 [BiometricIdentityFlow] Final state after validation:', JSON.stringify({
|
|
193
|
+
currentStep: finalState.currentStep,
|
|
194
|
+
hasResult: !!finalState.validationResult,
|
|
195
|
+
progress: finalState.progress,
|
|
196
|
+
isLoading: finalState.isLoading
|
|
197
|
+
}));
|
|
198
|
+
logger.info('Final state after validation:', finalState);
|
|
199
|
+
|
|
200
|
+
// Backup: If we have a result but the useEffect didn't fire, call the callback directly
|
|
201
|
+
if (finalState.currentStep === SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
202
|
+
console.log('🟢 [BiometricIdentityFlow] Backup: Calling onValidationComplete directly');
|
|
203
|
+
logger.info('Backup: Calling onValidationComplete directly after validation');
|
|
204
|
+
hasCalledValidationComplete.current = true;
|
|
205
|
+
onValidationCompleteRef.current(finalState.validationResult);
|
|
206
|
+
}
|
|
161
207
|
}
|
|
162
208
|
} catch (error) {
|
|
163
|
-
logger.error('Capture error:', error);
|
|
164
|
-
|
|
209
|
+
logger.error('Capture/validation error:', error);
|
|
210
|
+
const biometricError: BiometricError = {
|
|
165
211
|
name: 'BiometricError',
|
|
166
212
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
167
213
|
code: 'LIVENESS_CHECK_FAILED',
|
|
168
|
-
} as BiometricError
|
|
214
|
+
} as BiometricError;
|
|
215
|
+
onErrorRef.current(biometricError);
|
|
169
216
|
}
|
|
170
|
-
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity,
|
|
217
|
+
}, [cameraMode, uploadFrontID, uploadBackID, storeVideoRecording, validateIdentity, sdk]);
|
|
171
218
|
|
|
172
219
|
/**
|
|
173
220
|
* Handle retry
|
|
174
221
|
*/
|
|
175
222
|
const handleRetry = useCallback(() => {
|
|
223
|
+
hasCalledValidationComplete.current = false;
|
|
176
224
|
reset();
|
|
177
225
|
setCurrentChallenges([]);
|
|
178
226
|
setShowInstructions(true);
|
|
@@ -234,28 +282,33 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
234
282
|
);
|
|
235
283
|
}
|
|
236
284
|
|
|
237
|
-
// Show
|
|
238
|
-
|
|
285
|
+
// Show result (check this first, even if step is still VALIDATING but result exists)
|
|
286
|
+
// This handles edge cases where state transition might be delayed
|
|
287
|
+
if (state.validationResult && (state.currentStep === SDKStep.RESULT || state.progress >= 100)) {
|
|
288
|
+
// If we have a result but step is still VALIDATING, force transition to RESULT
|
|
289
|
+
if (state.currentStep === SDKStep.VALIDATING && state.progress >= 100) {
|
|
290
|
+
logger.info('Result available but step still VALIDATING, showing ResultScreen');
|
|
291
|
+
}
|
|
239
292
|
return (
|
|
240
|
-
<
|
|
241
|
-
|
|
293
|
+
<ResultScreen
|
|
294
|
+
result={state.validationResult}
|
|
242
295
|
theme={theme}
|
|
243
296
|
language={language}
|
|
297
|
+
onClose={() => {
|
|
298
|
+
// Callback already called automatically when RESULT step was reached
|
|
299
|
+
// This onClose is just for UI cleanup/navigation
|
|
300
|
+
}}
|
|
244
301
|
/>
|
|
245
302
|
);
|
|
246
303
|
}
|
|
247
304
|
|
|
248
|
-
// Show
|
|
249
|
-
if (state.currentStep === SDKStep.
|
|
305
|
+
// Show validation progress
|
|
306
|
+
if (state.currentStep === SDKStep.VALIDATING) {
|
|
250
307
|
return (
|
|
251
|
-
<
|
|
252
|
-
|
|
308
|
+
<ValidationProgress
|
|
309
|
+
progress={state.progress}
|
|
253
310
|
theme={theme}
|
|
254
311
|
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
312
|
/>
|
|
260
313
|
);
|
|
261
314
|
}
|
|
@@ -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,71 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
193
194
|
* Validate identity with all collected data
|
|
194
195
|
*/
|
|
195
196
|
const validateIdentity = useCallback(async () => {
|
|
197
|
+
console.log('🔵 [useBiometricSDK] validateIdentity called');
|
|
198
|
+
logger.info('validateIdentity called, current state:', sdk.getState());
|
|
196
199
|
setState(sdk.getState());
|
|
197
200
|
|
|
198
201
|
const pollInterval = setInterval(() => {
|
|
199
202
|
if (isMounted.current) {
|
|
200
|
-
|
|
203
|
+
const currentState = sdk.getState();
|
|
204
|
+
setState(currentState);
|
|
201
205
|
}
|
|
202
206
|
}, 200);
|
|
203
207
|
|
|
204
208
|
try {
|
|
209
|
+
console.log('🔵 [useBiometricSDK] Calling sdk.validateIdentity()...');
|
|
205
210
|
const result = await sdk.validateIdentity();
|
|
206
|
-
|
|
211
|
+
console.log('🟢 [useBiometricSDK] sdk.validateIdentity() completed, result:', result);
|
|
212
|
+
|
|
213
|
+
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
214
|
+
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
215
|
+
let pollCount = 0;
|
|
216
|
+
const maxPolls = 10; // Poll for up to 2 seconds (10 * 200ms)
|
|
217
|
+
|
|
218
|
+
const finalPollInterval = setInterval(() => {
|
|
219
|
+
pollCount++;
|
|
220
|
+
if (isMounted.current) {
|
|
221
|
+
const currentState = sdk.getState();
|
|
222
|
+
logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
223
|
+
setState(currentState);
|
|
224
|
+
|
|
225
|
+
// Stop polling once we reach RESULT or ERROR state
|
|
226
|
+
if (currentState.currentStep === SDKStep.RESULT || currentState.currentStep === SDKStep.ERROR) {
|
|
227
|
+
console.log('🟢 [useBiometricSDK] Reached final state:', currentState.currentStep);
|
|
228
|
+
clearInterval(finalPollInterval);
|
|
229
|
+
clearInterval(pollInterval);
|
|
230
|
+
logger.info('Reached final state, stopped polling:', currentState);
|
|
231
|
+
} else if (pollCount >= maxPolls) {
|
|
232
|
+
// Force stop after max polls
|
|
233
|
+
clearInterval(finalPollInterval);
|
|
234
|
+
clearInterval(pollInterval);
|
|
235
|
+
const finalState = sdk.getState();
|
|
236
|
+
logger.warn('Max polls reached, forcing final state:', finalState);
|
|
237
|
+
setState(finalState);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}, 200);
|
|
207
241
|
|
|
242
|
+
// Also get immediate state after promise resolves
|
|
208
243
|
if (isMounted.current) {
|
|
209
|
-
|
|
244
|
+
const immediateState = sdk.getState();
|
|
245
|
+
logger.info('Validation result received, immediate state:', immediateState);
|
|
246
|
+
setState(immediateState);
|
|
247
|
+
|
|
248
|
+
// If we're already at RESULT, clear intervals immediately
|
|
249
|
+
if (immediateState.currentStep === SDKStep.RESULT || immediateState.currentStep === SDKStep.ERROR) {
|
|
250
|
+
clearInterval(finalPollInterval);
|
|
251
|
+
clearInterval(pollInterval);
|
|
252
|
+
}
|
|
210
253
|
}
|
|
254
|
+
|
|
211
255
|
return result;
|
|
212
256
|
} catch (error) {
|
|
213
257
|
clearInterval(pollInterval);
|
|
214
258
|
if (isMounted.current) {
|
|
215
|
-
|
|
259
|
+
const errorState = sdk.getState();
|
|
260
|
+
logger.error('Validation error, state:', errorState, error);
|
|
261
|
+
setState(errorState);
|
|
216
262
|
}
|
|
217
263
|
throw error;
|
|
218
264
|
}
|