@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.
@@ -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,CAgTtE,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
@@ -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.validationResult) {
75
- onValidationComplete(state.validationResult);
92
+ if (state.currentStep !== biometric_identity_sdk_core_1.SDKStep.RESULT) {
93
+ hasCalledValidationComplete.current = false;
76
94
  }
77
- }, [state.validationResult, onValidationComplete]);
95
+ }, [state.currentStep]);
78
96
  // Handle error
79
97
  (0, react_1.useEffect)(() => {
80
98
  if (state.error) {
81
- 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);
82
102
  }
83
- }, [state.error, onError]);
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
- onError({
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, onError]);
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 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.14",
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'
@@ -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(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);
100
114
  }
101
- }, [state.validationResult, onValidationComplete]);
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
- onError(state.error);
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, onError]);
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
- onError({
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, onError]);
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 validation progress
237
- 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
+ }
238
281
  return (
239
- <ValidationProgress
240
- progress={state.progress}
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 result
248
- if (state.currentStep === SDKStep.RESULT && state.validationResult) {
294
+ // Show validation progress
295
+ if (state.currentStep === SDKStep.VALIDATING) {
249
296
  return (
250
- <ResultScreen
251
- result={state.validationResult}
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 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
  }