@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.
@@ -1 +1 @@
1
- {"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,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,CAoTtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
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
- if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
75
- // Call callback automatically when validation completes
76
- onValidationComplete(state.validationResult);
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, state.validationResult, onValidationComplete]);
97
+ }, [state.currentStep]);
79
98
  // Handle error
80
99
  (0, react_1.useEffect)(() => {
81
100
  if (state.error) {
82
- onError(state.error);
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, onError]);
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
- onError({
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, onError]);
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 validation progress
174
- if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
175
- return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
176
- }
177
- // Show result
178
- if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
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 stay still', icon: '📷' },
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,EAEZ,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,qBAuNlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
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
- setState(sdk.getState());
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
- clearInterval(pollInterval);
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
- setState(sdk.getState());
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
- setState(sdk.getState());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.15",
3
+ "version": "1.0.17",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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
- if (state.currentStep === SDKStep.RESULT && state.validationResult) {
99
- // Call callback automatically when validation completes
100
- onValidationComplete(state.validationResult);
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, state.validationResult, onValidationComplete]);
124
+ }, [state.currentStep]);
103
125
 
104
126
  // Handle error
105
127
  useEffect(() => {
106
128
  if (state.error) {
107
- onError(state.error);
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, onError]);
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
- onError({
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, onError]);
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 validation progress
238
- if (state.currentStep === SDKStep.VALIDATING) {
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
- <ValidationProgress
241
- progress={state.progress}
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 result
249
- if (state.currentStep === SDKStep.RESULT && state.validationResult) {
305
+ // Show validation progress
306
+ if (state.currentStep === SDKStep.VALIDATING) {
250
307
  return (
251
- <ResultScreen
252
- result={state.validationResult}
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 stay still', icon: '📷' },
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
- setState(sdk.getState());
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
- clearInterval(pollInterval);
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
- setState(sdk.getState());
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
- setState(sdk.getState());
259
+ const errorState = sdk.getState();
260
+ logger.error('Validation error, state:', errorState, error);
261
+ setState(errorState);
216
262
  }
217
263
  throw error;
218
264
  }