@hexar/biometric-identity-sdk-react-native 1.0.9 → 1.0.11

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,EAClB,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,CAiTtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
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,EAClB,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,CAiUtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
@@ -109,17 +109,28 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
109
109
  * Handle capture completion
110
110
  */
111
111
  const handleCaptureComplete = (0, react_1.useCallback)(async (data) => {
112
+ console.log('handleCaptureComplete called, cameraMode:', cameraMode);
112
113
  setShowCamera(false);
113
114
  try {
114
115
  if (cameraMode === 'front') {
116
+ console.log('Uploading front ID');
115
117
  await uploadFrontID(data);
118
+ console.log('Front ID uploaded successfully');
116
119
  }
117
120
  else if (cameraMode === 'back') {
121
+ console.log('Uploading back ID');
118
122
  await uploadBackID(data);
123
+ console.log('Back ID uploaded successfully');
119
124
  }
120
125
  else if (cameraMode === 'video') {
126
+ console.log('Processing video recording result');
121
127
  // Handle video recording result
122
128
  const videoResult = data;
129
+ console.log('Storing video recording:', {
130
+ frames: videoResult.frames.length,
131
+ duration: videoResult.duration,
132
+ challengesCompleted: videoResult.challengesCompleted.length
133
+ });
123
134
  await storeVideoRecording({
124
135
  frames: videoResult.frames,
125
136
  duration: videoResult.duration,
@@ -128,8 +139,10 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
128
139
  challengesCompleted: videoResult.challengesCompleted,
129
140
  sessionId: videoResult.sessionId,
130
141
  });
142
+ console.log('Video recording stored, starting validation...');
131
143
  // Automatically start validation after video
132
- await validateIdentity();
144
+ const result = await validateIdentity();
145
+ console.log('Validation complete, result:', result);
133
146
  }
134
147
  }
135
148
  catch (error) {
@@ -172,10 +185,12 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
172
185
  }
173
186
  // Show validation progress
174
187
  if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
188
+ console.log('Rendering ValidationProgress, progress:', state.progress);
175
189
  return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
176
190
  }
177
191
  // Show result
178
192
  if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
193
+ console.log('Rendering ResultScreen, result:', state.validationResult);
179
194
  return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => onValidationComplete(state.validationResult) }));
180
195
  }
181
196
  // Show error
@@ -1 +1 @@
1
- {"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAGlI,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAiwBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAA2B,MAAM,oCAAoC,CAAC;AAGlI,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA+CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqyBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
@@ -116,6 +116,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
116
116
  const frameCaptureInterval = (0, react_1.useRef)(null);
117
117
  const videoRecordingRef = (0, react_1.useRef)(null);
118
118
  const isRecordingRef = (0, react_1.useRef)(false);
119
+ const recordingTimeoutRef = (0, react_1.useRef)(null);
119
120
  const minDurationMs = 8000;
120
121
  const totalDuration = duration || Math.max(minDurationMs, challenges.reduce((sum, c) => sum + c.duration_ms, 0) + 2000);
121
122
  // Check camera permissions
@@ -274,16 +275,16 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
274
275
  react_native_1.Alert.alert('Recording Error', 'Failed to record video. Please try again.', [{ text: 'OK', onPress: onCancel }]);
275
276
  }, [onCancel]);
276
277
  const handleVideoComplete = (0, react_1.useCallback)(async (video) => {
277
- react_native_1.Alert.alert('Video Complete', `handleVideoComplete called\nVideo path: ${video?.path || 'N/A'}`);
278
+ console.log('handleVideoComplete called with video:', video?.path);
278
279
  if (!isRecordingRef.current && phase !== 'processing') {
279
- react_native_1.Alert.alert('Info', 'Video already processed, ignoring duplicate callback');
280
+ console.log('Video already processed, ignoring duplicate callback');
280
281
  return;
281
282
  }
282
283
  try {
283
284
  setPhase('processing');
284
285
  setOverallProgress(100);
285
286
  const actualDuration = Date.now() - recordingStartTime.current;
286
- react_native_1.Alert.alert('Video Processing', `Duration: ${(actualDuration / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s\nFrames: ${frames.length}`);
287
+ console.log('Video Processing - Duration:', (actualDuration / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's, Frames:', frames.length);
287
288
  if (actualDuration < minDurationMs) {
288
289
  setPhase('recording');
289
290
  setOverallProgress(0);
@@ -295,15 +296,15 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
295
296
  try {
296
297
  const RNFS = require('react-native-fs');
297
298
  videoBase64 = await RNFS.readFile(video.path, 'base64');
298
- react_native_1.Alert.alert('Video File Read', `Successfully read video file\nSize: ${videoBase64.length} bytes`);
299
+ console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
299
300
  }
300
301
  catch (fsError) {
301
- react_native_1.Alert.alert('Video File Warning', `Could not read video file, using captured frames\nError: ${fsError}`);
302
+ console.warn('Could not read video file, using captured frames:', fsError);
302
303
  }
303
304
  }
304
305
  const finalFrames = frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []);
305
306
  if (finalFrames.length === 0) {
306
- react_native_1.Alert.alert('Error', 'No frames available, cannot complete');
307
+ console.error('No frames available, cannot complete');
307
308
  setPhase('recording');
308
309
  handleRecordingError(new Error('No video frames captured'));
309
310
  return;
@@ -316,12 +317,18 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
316
317
  challengesCompleted: completedChallenges,
317
318
  sessionId,
318
319
  };
319
- react_native_1.Alert.alert('Video Completed Successfully', `Duration: ${(actualDuration / 1000).toFixed(1)}s\nFrames: ${result.frames.length}\nChallenges: ${completedChallenges.length}/${challenges.length}\nInstructions followed: ${result.instructionsFollowed ? 'Yes' : 'No'}\nQuality: ${result.qualityScore.toFixed(0)}%`);
320
+ console.log('Video recording completed successfully:', {
321
+ duration: (actualDuration / 1000).toFixed(1) + 's',
322
+ frames: result.frames.length,
323
+ challenges: `${completedChallenges.length}/${challenges.length}`,
324
+ instructionsFollowed: result.instructionsFollowed,
325
+ quality: result.qualityScore.toFixed(0) + '%'
326
+ });
320
327
  isRecordingRef.current = false;
321
328
  onComplete(result);
322
329
  }
323
330
  catch (error) {
324
- react_native_1.Alert.alert('Error Processing Video', `Error: ${error instanceof Error ? error.message : String(error)}`);
331
+ console.error('Error processing video:', error);
325
332
  setPhase('recording');
326
333
  setOverallProgress(0);
327
334
  handleRecordingError(error);
@@ -329,7 +336,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
329
336
  }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
330
337
  const startFrameCapture = (0, react_1.useCallback)(() => {
331
338
  if (cameraRef.current && device) {
332
- react_native_1.Alert.alert('Frame Capture', 'Starting frame capture mode');
339
+ console.log('Starting frame capture mode');
333
340
  frameCaptureInterval.current = setInterval(async () => {
334
341
  try {
335
342
  const photo = await cameraRef.current?.takePhoto({
@@ -357,32 +364,43 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
357
364
  }
358
365
  }
359
366
  catch (error) {
360
- react_native_1.Alert.alert('Frame Capture Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
367
+ console.warn('Frame capture error:', error);
361
368
  }
362
369
  }, 100);
363
370
  }
364
371
  }, [device]);
365
372
  const stopRecording = (0, react_1.useCallback)(async () => {
366
- react_native_1.Alert.alert('Stop Recording', 'Stopping recording...');
373
+ console.log('stopRecording called, isRecording:', isRecordingRef.current);
374
+ // Clear any pending timeouts
375
+ if (recordingTimeoutRef.current) {
376
+ clearTimeout(recordingTimeoutRef.current);
377
+ recordingTimeoutRef.current = null;
378
+ }
367
379
  if (frameCaptureInterval.current) {
368
380
  clearInterval(frameCaptureInterval.current);
369
381
  frameCaptureInterval.current = null;
370
382
  }
383
+ // Prevent multiple calls
384
+ if (!isRecordingRef.current) {
385
+ console.log('Recording already stopped, ignoring');
386
+ return;
387
+ }
371
388
  if (videoRecordingRef.current) {
372
389
  try {
373
- react_native_1.Alert.alert('Stop Recording', 'Stopping video recording...');
390
+ console.log('Stopping video recording, current ref:', !!videoRecordingRef.current);
374
391
  const recording = videoRecordingRef.current;
375
392
  videoRecordingRef.current = null;
376
393
  isRecordingRef.current = false;
377
394
  await recording.stop();
378
- react_native_1.Alert.alert('Recording Stopped', 'Video recording stopped\nWaiting for onRecordingFinished callback');
395
+ console.log('Video recording stopped - waiting for onRecordingFinished callback');
379
396
  }
380
397
  catch (error) {
381
- react_native_1.Alert.alert('Error Stopping Recording', `Error: ${error instanceof Error ? error.message : String(error)}`);
398
+ console.error('Error stopping video recording:', error);
382
399
  isRecordingRef.current = false;
383
400
  const actualDuration = Date.now() - recordingStartTime.current;
401
+ console.log('Error duration check:', actualDuration, 'minDurationMs:', minDurationMs, 'frames:', frames.length);
384
402
  if (actualDuration >= minDurationMs && frames.length > 0) {
385
- react_native_1.Alert.alert('Using Captured Frames', 'Video stopped with error, using captured frames');
403
+ console.log('Video stopped with error, using captured frames');
386
404
  const result = {
387
405
  frames,
388
406
  duration: actualDuration,
@@ -396,32 +414,42 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
396
414
  }
397
415
  }
398
416
  else {
417
+ console.log('No video recording ref, just setting flag to false');
399
418
  isRecordingRef.current = false;
400
419
  }
401
420
  }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
402
421
  const runChallenge = (0, react_1.useCallback)((index) => {
403
422
  if (index >= challenges.length) {
404
- react_native_1.Alert.alert('Challenges Complete', 'All challenges completed, stopping recording');
423
+ console.log('All challenges completed, checking duration');
405
424
  setOverallProgress(100);
425
+ // Clear the totalDuration timeout since we're handling it manually
426
+ if (recordingTimeoutRef.current) {
427
+ clearTimeout(recordingTimeoutRef.current);
428
+ recordingTimeoutRef.current = null;
429
+ console.log('Cleared totalDuration timeout');
430
+ }
406
431
  if (isRecordingRef.current) {
407
432
  const elapsed = Date.now() - recordingStartTime.current;
408
- react_native_1.Alert.alert('Checking Duration', `Elapsed: ${(elapsed / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s`);
433
+ console.log('Checking duration - Elapsed:', (elapsed / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's');
409
434
  if (elapsed < minDurationMs) {
410
435
  const remaining = minDurationMs - elapsed;
411
- react_native_1.Alert.alert('Waiting', `Waiting additional ${(remaining / 1000).toFixed(1)}s to meet minimum duration`);
412
- setTimeout(() => {
436
+ console.log('Waiting additional', (remaining / 1000).toFixed(1), 's to meet minimum duration');
437
+ recordingTimeoutRef.current = setTimeout(() => {
438
+ console.log('Minimum duration wait completed, stopping recording');
413
439
  if (isRecordingRef.current) {
414
440
  stopRecording();
415
441
  }
416
442
  }, remaining);
417
443
  return;
418
444
  }
445
+ console.log('Duration requirement met, stopping immediately');
419
446
  stopRecording();
420
447
  }
421
448
  else {
449
+ console.log('Recording already stopped, checking if we can complete');
422
450
  const actualDuration = Date.now() - recordingStartTime.current;
423
451
  if (actualDuration >= minDurationMs && frames.length > 0) {
424
- react_native_1.Alert.alert('Completing', 'Recording already stopped, completing with frames');
452
+ console.log('Recording already stopped, completing with frames');
425
453
  const result = {
426
454
  frames,
427
455
  duration: actualDuration,
@@ -477,42 +505,57 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
477
505
  setPhase('recording');
478
506
  recordingStartTime.current = Date.now();
479
507
  isRecordingRef.current = true;
480
- react_native_1.Alert.alert('Start Recording', `Starting video recording\nTotal duration: ${(totalDuration / 1000).toFixed(1)}s`);
508
+ console.log('Starting video recording, total duration:', (totalDuration / 1000).toFixed(1), 's');
481
509
  if (cameraRef.current && device) {
482
510
  try {
483
511
  videoRecordingRef.current = await cameraRef.current.startRecording({
484
512
  flash: 'off',
485
513
  onRecordingFinished: (video) => {
486
- react_native_1.Alert.alert('Recording Finished', `Video recording finished callback called\nPath: ${video?.path || 'N/A'}`);
487
- handleVideoComplete(video);
514
+ console.log('Video recording finished callback called, path:', video?.path || 'N/A', 'phase:', phase);
515
+ // Ensure we're in the right state to process
516
+ if (phase === 'recording' || phase === 'processing') {
517
+ handleVideoComplete(video);
518
+ }
519
+ else {
520
+ console.warn('Received onRecordingFinished but phase is', phase, '- calling handleVideoComplete anyway');
521
+ handleVideoComplete(video);
522
+ }
488
523
  },
489
524
  onRecordingError: (error) => {
490
- react_native_1.Alert.alert('Recording Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
525
+ console.error('Recording error:', error);
491
526
  handleRecordingError(error);
492
527
  },
493
528
  });
494
- react_native_1.Alert.alert('Recording Started', 'Video recording started successfully');
529
+ console.log('Video recording started successfully');
495
530
  }
496
531
  catch (error) {
497
- react_native_1.Alert.alert('Recording Fallback', `Video recording not available, falling back to frame capture\nError: ${error}`);
532
+ console.warn('Video recording not available, falling back to frame capture:', error);
498
533
  startFrameCapture();
499
534
  }
500
535
  }
501
536
  else {
502
- react_native_1.Alert.alert('Camera Not Available', 'Camera not available, using frame capture');
537
+ console.log('Camera not available, using frame capture');
503
538
  startFrameCapture();
504
539
  }
505
540
  runChallenge(0);
506
- const timeoutId = setTimeout(() => {
507
- react_native_1.Alert.alert('Timeout', 'Recording timeout reached, stopping recording');
541
+ // Set a safety timeout that's longer than totalDuration to ensure we stop eventually
542
+ // This will be cleared if challenges complete early
543
+ const maxDuration = Math.max(totalDuration, minDurationMs + 2000); // Add 2s buffer
544
+ recordingTimeoutRef.current = setTimeout(() => {
545
+ console.log('Safety timeout reached (max duration), stopping recording');
508
546
  if (isRecordingRef.current) {
509
547
  stopRecording().catch(err => {
510
- react_native_1.Alert.alert('Timeout Error', `Error stopping recording on timeout: ${err}`);
548
+ console.error('Error stopping recording on safety timeout:', err);
511
549
  });
512
550
  }
513
- }, totalDuration);
514
- return () => clearTimeout(timeoutId);
515
- }, [device, totalDuration, handleVideoComplete, handleRecordingError, runChallenge, stopRecording, startFrameCapture]);
551
+ }, maxDuration);
552
+ return () => {
553
+ if (recordingTimeoutRef.current) {
554
+ clearTimeout(recordingTimeoutRef.current);
555
+ recordingTimeoutRef.current = null;
556
+ }
557
+ };
558
+ }, [device, totalDuration, minDurationMs, handleVideoComplete, handleRecordingError, runChallenge, stopRecording, startFrameCapture]);
516
559
  // Current challenge
517
560
  const currentChallenge = challenges[currentChallengeIndex];
518
561
  // Get direction arrow for the current challenge
@@ -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,EACZ,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,qBAqMlC,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,EACZ,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,qBA+NlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -137,11 +137,34 @@ const useBiometricSDK = () => {
137
137
  * Validate identity with all collected data
138
138
  */
139
139
  const validateIdentity = (0, react_1.useCallback)(async () => {
140
- const result = await sdk.validateIdentity();
141
- if (isMounted.current) {
142
- setState(sdk.getState());
140
+ console.log('validateIdentity called, current state:', sdk.getState().currentStep);
141
+ // Update state immediately to show validation screen
142
+ setState(sdk.getState());
143
+ // Poll state during validation to catch intermediate updates
144
+ const pollInterval = setInterval(() => {
145
+ if (isMounted.current) {
146
+ const currentState = sdk.getState();
147
+ setState(currentState);
148
+ console.log('State polled - step:', currentState.currentStep, 'progress:', currentState.progress);
149
+ }
150
+ }, 200);
151
+ try {
152
+ const result = await sdk.validateIdentity();
153
+ clearInterval(pollInterval);
154
+ if (isMounted.current) {
155
+ const finalState = sdk.getState();
156
+ setState(finalState);
157
+ console.log('Validation complete, final state:', finalState.currentStep, 'result:', result);
158
+ }
159
+ return result;
160
+ }
161
+ catch (error) {
162
+ clearInterval(pollInterval);
163
+ if (isMounted.current) {
164
+ setState(sdk.getState());
165
+ }
166
+ throw error;
143
167
  }
144
- return result;
145
168
  }, [sdk]);
146
169
  /**
147
170
  * Reset SDK state
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -135,17 +135,29 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
135
135
  * Handle capture completion
136
136
  */
137
137
  const handleCaptureComplete = useCallback(async (data: any) => {
138
+ console.log('handleCaptureComplete called, cameraMode:', cameraMode);
138
139
  setShowCamera(false);
139
140
 
140
141
  try {
141
142
  if (cameraMode === 'front') {
143
+ console.log('Uploading front ID');
142
144
  await uploadFrontID(data);
145
+ console.log('Front ID uploaded successfully');
143
146
  } else if (cameraMode === 'back') {
147
+ console.log('Uploading back ID');
144
148
  await uploadBackID(data);
149
+ console.log('Back ID uploaded successfully');
145
150
  } else if (cameraMode === 'video') {
151
+ console.log('Processing video recording result');
146
152
  // Handle video recording result
147
153
  const videoResult: VideoRecordingResult = data;
148
154
 
155
+ console.log('Storing video recording:', {
156
+ frames: videoResult.frames.length,
157
+ duration: videoResult.duration,
158
+ challengesCompleted: videoResult.challengesCompleted.length
159
+ });
160
+
149
161
  await storeVideoRecording({
150
162
  frames: videoResult.frames,
151
163
  duration: videoResult.duration,
@@ -155,8 +167,10 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
155
167
  sessionId: videoResult.sessionId,
156
168
  });
157
169
 
170
+ console.log('Video recording stored, starting validation...');
158
171
  // Automatically start validation after video
159
- await validateIdentity();
172
+ const result = await validateIdentity();
173
+ console.log('Validation complete, result:', result);
160
174
  }
161
175
  } catch (error) {
162
176
  console.error('Capture error:', error);
@@ -235,6 +249,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
235
249
 
236
250
  // Show validation progress
237
251
  if (state.currentStep === SDKStep.VALIDATING) {
252
+ console.log('Rendering ValidationProgress, progress:', state.progress);
238
253
  return (
239
254
  <ValidationProgress
240
255
  progress={state.progress}
@@ -246,6 +261,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
246
261
 
247
262
  // Show result
248
263
  if (state.currentStep === SDKStep.RESULT && state.validationResult) {
264
+ console.log('Rendering ResultScreen, result:', state.validationResult);
249
265
  return (
250
266
  <ResultScreen
251
267
  result={state.validationResult}
@@ -147,6 +147,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
147
147
  const frameCaptureInterval = useRef<NodeJS.Timeout | null>(null);
148
148
  const videoRecordingRef = useRef<any>(null);
149
149
  const isRecordingRef = useRef<boolean>(false);
150
+ const recordingTimeoutRef = useRef<NodeJS.Timeout | null>(null);
150
151
  const minDurationMs = 8000;
151
152
  const totalDuration = duration || Math.max(
152
153
  minDurationMs,
@@ -328,10 +329,10 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
328
329
  }, [onCancel]);
329
330
 
330
331
  const handleVideoComplete = useCallback(async (video: any) => {
331
- Alert.alert('Video Complete', `handleVideoComplete called\nVideo path: ${video?.path || 'N/A'}`);
332
+ console.log('handleVideoComplete called with video:', video?.path);
332
333
 
333
334
  if (!isRecordingRef.current && phase !== 'processing') {
334
- Alert.alert('Info', 'Video already processed, ignoring duplicate callback');
335
+ console.log('Video already processed, ignoring duplicate callback');
335
336
  return;
336
337
  }
337
338
 
@@ -340,10 +341,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
340
341
  setOverallProgress(100);
341
342
 
342
343
  const actualDuration = Date.now() - recordingStartTime.current;
343
- Alert.alert(
344
- 'Video Processing',
345
- `Duration: ${(actualDuration / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s\nFrames: ${frames.length}`
346
- );
344
+ console.log('Video Processing - Duration:', (actualDuration / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's, Frames:', frames.length);
347
345
 
348
346
  if (actualDuration < minDurationMs) {
349
347
  setPhase('recording');
@@ -361,16 +359,16 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
361
359
  try {
362
360
  const RNFS = require('react-native-fs');
363
361
  videoBase64 = await RNFS.readFile(video.path, 'base64');
364
- Alert.alert('Video File Read', `Successfully read video file\nSize: ${videoBase64.length} bytes`);
362
+ console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
365
363
  } catch (fsError) {
366
- Alert.alert('Video File Warning', `Could not read video file, using captured frames\nError: ${fsError}`);
364
+ console.warn('Could not read video file, using captured frames:', fsError);
367
365
  }
368
366
  }
369
367
 
370
368
  const finalFrames = frames.length > 0 ? frames : (videoBase64 ? [videoBase64] : []);
371
369
 
372
370
  if (finalFrames.length === 0) {
373
- Alert.alert('Error', 'No frames available, cannot complete');
371
+ console.error('No frames available, cannot complete');
374
372
  setPhase('recording');
375
373
  handleRecordingError(new Error('No video frames captured'));
376
374
  return;
@@ -385,15 +383,18 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
385
383
  sessionId,
386
384
  };
387
385
 
388
- Alert.alert(
389
- 'Video Completed Successfully',
390
- `Duration: ${(actualDuration / 1000).toFixed(1)}s\nFrames: ${result.frames.length}\nChallenges: ${completedChallenges.length}/${challenges.length}\nInstructions followed: ${result.instructionsFollowed ? 'Yes' : 'No'}\nQuality: ${result.qualityScore.toFixed(0)}%`
391
- );
386
+ console.log('Video recording completed successfully:', {
387
+ duration: (actualDuration / 1000).toFixed(1) + 's',
388
+ frames: result.frames.length,
389
+ challenges: `${completedChallenges.length}/${challenges.length}`,
390
+ instructionsFollowed: result.instructionsFollowed,
391
+ quality: result.qualityScore.toFixed(0) + '%'
392
+ });
392
393
 
393
394
  isRecordingRef.current = false;
394
395
  onComplete(result);
395
396
  } catch (error) {
396
- Alert.alert('Error Processing Video', `Error: ${error instanceof Error ? error.message : String(error)}`);
397
+ console.error('Error processing video:', error);
397
398
  setPhase('recording');
398
399
  setOverallProgress(0);
399
400
  handleRecordingError(error);
@@ -402,7 +403,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
402
403
 
403
404
  const startFrameCapture = useCallback(() => {
404
405
  if (cameraRef.current && device) {
405
- Alert.alert('Frame Capture', 'Starting frame capture mode');
406
+ console.log('Starting frame capture mode');
406
407
  frameCaptureInterval.current = setInterval(async () => {
407
408
  try {
408
409
  const photo = await cameraRef.current?.takePhoto({
@@ -429,36 +430,49 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
429
430
  }
430
431
  }
431
432
  } catch (error) {
432
- Alert.alert('Frame Capture Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
433
+ console.warn('Frame capture error:', error);
433
434
  }
434
435
  }, 100);
435
436
  }
436
437
  }, [device]);
437
438
 
438
439
  const stopRecording = useCallback(async () => {
439
- Alert.alert('Stop Recording', 'Stopping recording...');
440
+ console.log('stopRecording called, isRecording:', isRecordingRef.current);
441
+
442
+ // Clear any pending timeouts
443
+ if (recordingTimeoutRef.current) {
444
+ clearTimeout(recordingTimeoutRef.current);
445
+ recordingTimeoutRef.current = null;
446
+ }
440
447
 
441
448
  if (frameCaptureInterval.current) {
442
449
  clearInterval(frameCaptureInterval.current);
443
450
  frameCaptureInterval.current = null;
444
451
  }
445
452
 
453
+ // Prevent multiple calls
454
+ if (!isRecordingRef.current) {
455
+ console.log('Recording already stopped, ignoring');
456
+ return;
457
+ }
458
+
446
459
  if (videoRecordingRef.current) {
447
460
  try {
448
- Alert.alert('Stop Recording', 'Stopping video recording...');
461
+ console.log('Stopping video recording, current ref:', !!videoRecordingRef.current);
449
462
  const recording = videoRecordingRef.current;
450
463
  videoRecordingRef.current = null;
451
464
  isRecordingRef.current = false;
452
465
 
453
466
  await recording.stop();
454
- Alert.alert('Recording Stopped', 'Video recording stopped\nWaiting for onRecordingFinished callback');
467
+ console.log('Video recording stopped - waiting for onRecordingFinished callback');
455
468
  } catch (error) {
456
- Alert.alert('Error Stopping Recording', `Error: ${error instanceof Error ? error.message : String(error)}`);
469
+ console.error('Error stopping video recording:', error);
457
470
  isRecordingRef.current = false;
458
471
 
459
472
  const actualDuration = Date.now() - recordingStartTime.current;
473
+ console.log('Error duration check:', actualDuration, 'minDurationMs:', minDurationMs, 'frames:', frames.length);
460
474
  if (actualDuration >= minDurationMs && frames.length > 0) {
461
- Alert.alert('Using Captured Frames', 'Video stopped with error, using captured frames');
475
+ console.log('Video stopped with error, using captured frames');
462
476
  const result: VideoRecordingResult = {
463
477
  frames,
464
478
  duration: actualDuration,
@@ -471,26 +485,32 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
471
485
  }
472
486
  }
473
487
  } else {
488
+ console.log('No video recording ref, just setting flag to false');
474
489
  isRecordingRef.current = false;
475
490
  }
476
491
  }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
477
492
 
478
493
  const runChallenge = useCallback((index: number) => {
479
494
  if (index >= challenges.length) {
480
- Alert.alert('Challenges Complete', 'All challenges completed, stopping recording');
495
+ console.log('All challenges completed, checking duration');
481
496
  setOverallProgress(100);
482
497
 
498
+ // Clear the totalDuration timeout since we're handling it manually
499
+ if (recordingTimeoutRef.current) {
500
+ clearTimeout(recordingTimeoutRef.current);
501
+ recordingTimeoutRef.current = null;
502
+ console.log('Cleared totalDuration timeout');
503
+ }
504
+
483
505
  if (isRecordingRef.current) {
484
506
  const elapsed = Date.now() - recordingStartTime.current;
485
- Alert.alert(
486
- 'Checking Duration',
487
- `Elapsed: ${(elapsed / 1000).toFixed(1)}s\nMin required: ${(minDurationMs / 1000).toFixed(1)}s`
488
- );
507
+ console.log('Checking duration - Elapsed:', (elapsed / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's');
489
508
 
490
509
  if (elapsed < minDurationMs) {
491
510
  const remaining = minDurationMs - elapsed;
492
- Alert.alert('Waiting', `Waiting additional ${(remaining / 1000).toFixed(1)}s to meet minimum duration`);
493
- setTimeout(() => {
511
+ console.log('Waiting additional', (remaining / 1000).toFixed(1), 's to meet minimum duration');
512
+ recordingTimeoutRef.current = setTimeout(() => {
513
+ console.log('Minimum duration wait completed, stopping recording');
494
514
  if (isRecordingRef.current) {
495
515
  stopRecording();
496
516
  }
@@ -498,11 +518,13 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
498
518
  return;
499
519
  }
500
520
 
521
+ console.log('Duration requirement met, stopping immediately');
501
522
  stopRecording();
502
523
  } else {
524
+ console.log('Recording already stopped, checking if we can complete');
503
525
  const actualDuration = Date.now() - recordingStartTime.current;
504
526
  if (actualDuration >= minDurationMs && frames.length > 0) {
505
- Alert.alert('Completing', 'Recording already stopped, completing with frames');
527
+ console.log('Recording already stopped, completing with frames');
506
528
  const result: VideoRecordingResult = {
507
529
  frames,
508
530
  duration: actualDuration,
@@ -569,44 +591,58 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
569
591
  recordingStartTime.current = Date.now();
570
592
  isRecordingRef.current = true;
571
593
 
572
- Alert.alert('Start Recording', `Starting video recording\nTotal duration: ${(totalDuration / 1000).toFixed(1)}s`);
594
+ console.log('Starting video recording, total duration:', (totalDuration / 1000).toFixed(1), 's');
573
595
 
574
596
  if (cameraRef.current && device) {
575
597
  try {
576
598
  videoRecordingRef.current = await cameraRef.current.startRecording({
577
599
  flash: 'off',
578
600
  onRecordingFinished: (video: any) => {
579
- Alert.alert('Recording Finished', `Video recording finished callback called\nPath: ${video?.path || 'N/A'}`);
580
- handleVideoComplete(video);
601
+ console.log('Video recording finished callback called, path:', video?.path || 'N/A', 'phase:', phase);
602
+ // Ensure we're in the right state to process
603
+ if (phase === 'recording' || phase === 'processing') {
604
+ handleVideoComplete(video);
605
+ } else {
606
+ console.warn('Received onRecordingFinished but phase is', phase, '- calling handleVideoComplete anyway');
607
+ handleVideoComplete(video);
608
+ }
581
609
  },
582
610
  onRecordingError: (error: any) => {
583
- Alert.alert('Recording Error', `Error: ${error instanceof Error ? error.message : String(error)}`);
611
+ console.error('Recording error:', error);
584
612
  handleRecordingError(error);
585
613
  },
586
614
  });
587
- Alert.alert('Recording Started', 'Video recording started successfully');
615
+ console.log('Video recording started successfully');
588
616
  } catch (error) {
589
- Alert.alert('Recording Fallback', `Video recording not available, falling back to frame capture\nError: ${error}`);
617
+ console.warn('Video recording not available, falling back to frame capture:', error);
590
618
  startFrameCapture();
591
619
  }
592
620
  } else {
593
- Alert.alert('Camera Not Available', 'Camera not available, using frame capture');
621
+ console.log('Camera not available, using frame capture');
594
622
  startFrameCapture();
595
623
  }
596
624
 
597
625
  runChallenge(0);
598
626
 
599
- const timeoutId = setTimeout(() => {
600
- Alert.alert('Timeout', 'Recording timeout reached, stopping recording');
627
+ // Set a safety timeout that's longer than totalDuration to ensure we stop eventually
628
+ // This will be cleared if challenges complete early
629
+ const maxDuration = Math.max(totalDuration, minDurationMs + 2000); // Add 2s buffer
630
+ recordingTimeoutRef.current = setTimeout(() => {
631
+ console.log('Safety timeout reached (max duration), stopping recording');
601
632
  if (isRecordingRef.current) {
602
633
  stopRecording().catch(err => {
603
- Alert.alert('Timeout Error', `Error stopping recording on timeout: ${err}`);
634
+ console.error('Error stopping recording on safety timeout:', err);
604
635
  });
605
636
  }
606
- }, totalDuration);
637
+ }, maxDuration);
607
638
 
608
- return () => clearTimeout(timeoutId);
609
- }, [device, totalDuration, handleVideoComplete, handleRecordingError, runChallenge, stopRecording, startFrameCapture]);
639
+ return () => {
640
+ if (recordingTimeoutRef.current) {
641
+ clearTimeout(recordingTimeoutRef.current);
642
+ recordingTimeoutRef.current = null;
643
+ }
644
+ };
645
+ }, [device, totalDuration, minDurationMs, handleVideoComplete, handleRecordingError, runChallenge, stopRecording, startFrameCapture]);
610
646
 
611
647
  // Current challenge
612
648
  const currentChallenge = challenges[currentChallengeIndex];
@@ -192,11 +192,37 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
192
192
  * Validate identity with all collected data
193
193
  */
194
194
  const validateIdentity = useCallback(async () => {
195
- const result = await sdk.validateIdentity();
196
- if (isMounted.current) {
197
- setState(sdk.getState());
195
+ console.log('validateIdentity called, current state:', sdk.getState().currentStep);
196
+
197
+ // Update state immediately to show validation screen
198
+ setState(sdk.getState());
199
+
200
+ // Poll state during validation to catch intermediate updates
201
+ const pollInterval = setInterval(() => {
202
+ if (isMounted.current) {
203
+ const currentState = sdk.getState();
204
+ setState(currentState);
205
+ console.log('State polled - step:', currentState.currentStep, 'progress:', currentState.progress);
206
+ }
207
+ }, 200);
208
+
209
+ try {
210
+ const result = await sdk.validateIdentity();
211
+ clearInterval(pollInterval);
212
+
213
+ if (isMounted.current) {
214
+ const finalState = sdk.getState();
215
+ setState(finalState);
216
+ console.log('Validation complete, final state:', finalState.currentStep, 'result:', result);
217
+ }
218
+ return result;
219
+ } catch (error) {
220
+ clearInterval(pollInterval);
221
+ if (isMounted.current) {
222
+ setState(sdk.getState());
223
+ }
224
+ throw error;
198
225
  }
199
- return result;
200
226
  }, [sdk]);
201
227
 
202
228
  /**