@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.
@@ -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,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
- // Call callback automatically when validation completes
76
- onValidationComplete(state.validationResult);
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, state.validationResult, onValidationComplete]);
95
+ }, [state.currentStep]);
79
96
  // Handle error
80
97
  (0, react_1.useEffect)(() => {
81
98
  if (state.error) {
82
- onError(state.error);
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, onError]);
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
- onError({
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, onError]);
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 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) {
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 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,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
- setState(sdk.getState());
144
+ const currentState = sdk.getState();
145
+ setState(currentState);
144
146
  }
145
147
  }, 200);
146
148
  try {
147
149
  const result = await sdk.validateIdentity();
148
- clearInterval(pollInterval);
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
- setState(sdk.getState());
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
- setState(sdk.getState());
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
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,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
- // Call callback automatically when validation completes
100
- onValidationComplete(state.validationResult);
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, state.validationResult, onValidationComplete]);
122
+ }, [state.currentStep]);
103
123
 
104
124
  // Handle error
105
125
  useEffect(() => {
106
126
  if (state.error) {
107
- onError(state.error);
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, onError]);
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
- onError({
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, onError]);
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 validation progress
238
- if (state.currentStep === SDKStep.VALIDATING) {
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
- <ValidationProgress
241
- progress={state.progress}
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 result
249
- if (state.currentStep === SDKStep.RESULT && state.validationResult) {
294
+ // Show validation progress
295
+ if (state.currentStep === SDKStep.VALIDATING) {
250
296
  return (
251
- <ResultScreen
252
- result={state.validationResult}
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 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,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
- setState(sdk.getState());
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
- setState(sdk.getState());
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
- setState(sdk.getState());
255
+ const errorState = sdk.getState();
256
+ logger.error('Validation error, state:', errorState, error);
257
+ setState(errorState);
216
258
  }
217
259
  throw error;
218
260
  }