@hexar/biometric-identity-sdk-react-native 1.0.18 → 1.0.20

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":"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,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,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;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA2xBtD,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,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,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;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqzBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
@@ -100,6 +100,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
100
100
  const [overallProgress, setOverallProgress] = (0, react_1.useState)(0);
101
101
  const [completedChallenges, setCompletedChallenges] = (0, react_1.useState)([]);
102
102
  const [frames, setFrames] = (0, react_1.useState)([]);
103
+ const framesRef = (0, react_1.useRef)([]);
103
104
  const [hasPermission, setHasPermission] = (0, react_1.useState)(false);
104
105
  const cameraRef = (0, react_1.useRef)(null);
105
106
  const { hasPermission: cameraPermission, requestPermission } = (0, react_native_vision_camera_1.useCameraPermission)();
@@ -257,6 +258,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
257
258
  }, [arrowAnim]);
258
259
  const resetAndRetry = (0, react_1.useCallback)(() => {
259
260
  setFrames([]);
261
+ framesRef.current = [];
260
262
  setCompletedChallenges([]);
261
263
  setCurrentChallengeIndex(0);
262
264
  setChallengeProgress(0);
@@ -293,16 +295,18 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
293
295
  biometric_identity_sdk_core_1.logger.warn('Could not read video file, using captured frames');
294
296
  }
295
297
  }
296
- let finalFrames = frames.length > 0 ? frames : [];
298
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
299
+ let finalFrames = currentFrames.length > 0 ? currentFrames : [];
297
300
  if (finalFrames.length === 0 && videoBase64) {
298
301
  finalFrames = [videoBase64];
299
302
  }
300
303
  if (finalFrames.length === 0) {
301
- biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete');
304
+ biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
302
305
  setPhase('recording');
303
306
  handleRecordingError(new Error('No video frames captured'));
304
307
  return;
305
308
  }
309
+ biometric_identity_sdk_core_1.logger.info('Using frames for completion:', finalFrames.length, 'frames');
306
310
  const result = {
307
311
  frames: finalFrames,
308
312
  duration: actualDuration,
@@ -324,7 +328,16 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
324
328
  }, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
325
329
  const startFrameCapture = (0, react_1.useCallback)(() => {
326
330
  if (cameraRef.current && device) {
331
+ biometric_identity_sdk_core_1.logger.info('Starting frame capture');
332
+ framesRef.current = [];
327
333
  frameCaptureInterval.current = setInterval(async () => {
334
+ if (!isRecordingRef.current) {
335
+ if (frameCaptureInterval.current) {
336
+ clearInterval(frameCaptureInterval.current);
337
+ frameCaptureInterval.current = null;
338
+ }
339
+ return;
340
+ }
328
341
  try {
329
342
  const photo = await cameraRef.current?.takePhoto({
330
343
  flash: 'off',
@@ -334,26 +347,32 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
334
347
  const RNFS = require('react-native-fs');
335
348
  const base64 = await RNFS.readFile(photo.path, 'base64');
336
349
  setFrames(prev => {
337
- if (prev.length < 100) {
338
- return [...prev, base64];
350
+ const newFrames = prev.length < 100 ? [...prev, base64] : prev;
351
+ framesRef.current = newFrames;
352
+ if (newFrames.length % 10 === 0) {
353
+ biometric_identity_sdk_core_1.logger.info('Captured frames:', newFrames.length);
339
354
  }
340
- return prev;
355
+ return newFrames;
341
356
  });
342
357
  }
343
358
  catch (fsError) {
359
+ biometric_identity_sdk_core_1.logger.warn('Could not read photo file, using path:', fsError);
344
360
  setFrames(prev => {
345
- if (prev.length < 100) {
346
- return [...prev, photo.path];
347
- }
348
- return prev;
361
+ const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
362
+ framesRef.current = newFrames;
363
+ return newFrames;
349
364
  });
350
365
  }
351
366
  }
352
367
  }
353
368
  catch (error) {
369
+ biometric_identity_sdk_core_1.logger.warn('Frame capture error:', error);
354
370
  }
355
371
  }, 100);
356
372
  }
373
+ else {
374
+ biometric_identity_sdk_core_1.logger.warn('Cannot start frame capture: camera or device not available');
375
+ }
357
376
  }, [device]);
358
377
  const stopRecording = (0, react_1.useCallback)(async () => {
359
378
  if (recordingTimeoutRef.current) {
@@ -377,23 +396,26 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
377
396
  catch (error) {
378
397
  biometric_identity_sdk_core_1.logger.error('Error stopping video recording:', error);
379
398
  const actualDuration = Date.now() - recordingStartTime.current;
380
- if (actualDuration >= minDurationMs && frames.length > 0) {
399
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
400
+ if (actualDuration >= minDurationMs && currentFrames.length > 0) {
401
+ biometric_identity_sdk_core_1.logger.info('Stopping recording with frames:', currentFrames.length);
381
402
  const result = {
382
- frames,
403
+ frames: currentFrames,
383
404
  duration: actualDuration,
384
405
  instructionsFollowed: completedChallenges.length === challenges.length,
385
- qualityScore: Math.min(100, (frames.length / 30) * 100),
406
+ qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
386
407
  challengesCompleted: completedChallenges,
387
408
  sessionId,
388
409
  };
389
410
  onComplete(result);
390
411
  }
391
412
  else {
413
+ biometric_identity_sdk_core_1.logger.info('Stopping recording with frames (fallback):', currentFrames.length);
392
414
  const result = {
393
- frames: frames.length > 0 ? frames : [],
415
+ frames: currentFrames.length > 0 ? currentFrames : [],
394
416
  duration: actualDuration,
395
417
  instructionsFollowed: completedChallenges.length === challenges.length,
396
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 0,
418
+ qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
397
419
  challengesCompleted: completedChallenges,
398
420
  sessionId,
399
421
  };
@@ -403,19 +425,21 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
403
425
  }
404
426
  else {
405
427
  const actualDuration = Date.now() - recordingStartTime.current;
406
- if (frames.length === 0 && actualDuration < minDurationMs) {
407
- biometric_identity_sdk_core_1.logger.error('No frames and duration too short, cannot complete');
428
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
429
+ if (currentFrames.length === 0 && actualDuration < minDurationMs) {
430
+ biometric_identity_sdk_core_1.logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
408
431
  setPhase('recording');
409
432
  react_native_1.Alert.alert(strings.errors.videoTooShort?.title || 'Recording Too Short', strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`, [{ text: strings.common.retry || 'OK', onPress: resetAndRetry }]);
410
433
  return;
411
434
  }
412
435
  setPhase('processing');
413
436
  setOverallProgress(100);
437
+ biometric_identity_sdk_core_1.logger.info('Completing recording with frames:', currentFrames.length);
414
438
  const result = {
415
- frames: frames.length > 0 ? frames : [],
439
+ frames: currentFrames.length > 0 ? currentFrames : [],
416
440
  duration: actualDuration,
417
441
  instructionsFollowed: completedChallenges.length === challenges.length,
418
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 0,
442
+ qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
419
443
  challengesCompleted: completedChallenges,
420
444
  sessionId,
421
445
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.0.18",
3
+ "version": "1.0.20",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,7 +11,7 @@
11
11
  "clean": "rm -rf dist"
12
12
  },
13
13
  "peerDependencies": {
14
- "@hexar/biometric-identity-sdk-core": ">=1.0.0",
14
+ "@hexar/biometric-identity-sdk-core": ">=1.0.11",
15
15
  "react": ">=18.0.0",
16
16
  "react-native": ">=0.70.0",
17
17
  "react-native-permissions": ">=4.0.0",
@@ -127,6 +127,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
127
127
  const [overallProgress, setOverallProgress] = useState(0);
128
128
  const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
129
129
  const [frames, setFrames] = useState<string[]>([]);
130
+ const framesRef = useRef<string[]>([]);
130
131
  const [hasPermission, setHasPermission] = useState(false);
131
132
 
132
133
  const cameraRef = useRef<Camera>(null);
@@ -304,6 +305,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
304
305
 
305
306
  const resetAndRetry = useCallback(() => {
306
307
  setFrames([]);
308
+ framesRef.current = [];
307
309
  setCompletedChallenges([]);
308
310
  setCurrentChallengeIndex(0);
309
311
  setChallengeProgress(0);
@@ -354,19 +356,22 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
354
356
  }
355
357
  }
356
358
 
357
- let finalFrames = frames.length > 0 ? frames : [];
359
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
360
+ let finalFrames = currentFrames.length > 0 ? currentFrames : [];
358
361
 
359
362
  if (finalFrames.length === 0 && videoBase64) {
360
363
  finalFrames = [videoBase64];
361
364
  }
362
365
 
363
366
  if (finalFrames.length === 0) {
364
- logger.error('No frames or video available, cannot complete');
367
+ logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
365
368
  setPhase('recording');
366
369
  handleRecordingError(new Error('No video frames captured'));
367
370
  return;
368
371
  }
369
372
 
373
+ logger.info('Using frames for completion:', finalFrames.length, 'frames');
374
+
370
375
  const result: VideoRecordingResult = {
371
376
  frames: finalFrames,
372
377
  duration: actualDuration,
@@ -390,7 +395,17 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
390
395
 
391
396
  const startFrameCapture = useCallback(() => {
392
397
  if (cameraRef.current && device) {
398
+ logger.info('Starting frame capture');
399
+ framesRef.current = [];
393
400
  frameCaptureInterval.current = setInterval(async () => {
401
+ if (!isRecordingRef.current) {
402
+ if (frameCaptureInterval.current) {
403
+ clearInterval(frameCaptureInterval.current);
404
+ frameCaptureInterval.current = null;
405
+ }
406
+ return;
407
+ }
408
+
394
409
  try {
395
410
  const photo = await cameraRef.current?.takePhoto({
396
411
  flash: 'off',
@@ -401,23 +416,28 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
401
416
  const RNFS = require('react-native-fs');
402
417
  const base64 = await RNFS.readFile(photo.path, 'base64');
403
418
  setFrames(prev => {
404
- if (prev.length < 100) {
405
- return [...prev, base64];
419
+ const newFrames = prev.length < 100 ? [...prev, base64] : prev;
420
+ framesRef.current = newFrames;
421
+ if (newFrames.length % 10 === 0) {
422
+ logger.info('Captured frames:', newFrames.length);
406
423
  }
407
- return prev;
424
+ return newFrames;
408
425
  });
409
426
  } catch (fsError) {
427
+ logger.warn('Could not read photo file, using path:', fsError);
410
428
  setFrames(prev => {
411
- if (prev.length < 100) {
412
- return [...prev, photo.path];
413
- }
414
- return prev;
429
+ const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
430
+ framesRef.current = newFrames;
431
+ return newFrames;
415
432
  });
416
433
  }
417
434
  }
418
435
  } catch (error) {
436
+ logger.warn('Frame capture error:', error);
419
437
  }
420
438
  }, 100);
439
+ } else {
440
+ logger.warn('Cannot start frame capture: camera or device not available');
421
441
  }
422
442
  }, [device]);
423
443
 
@@ -447,22 +467,26 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
447
467
  logger.error('Error stopping video recording:', error);
448
468
 
449
469
  const actualDuration = Date.now() - recordingStartTime.current;
450
- if (actualDuration >= minDurationMs && frames.length > 0) {
470
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
471
+
472
+ if (actualDuration >= minDurationMs && currentFrames.length > 0) {
473
+ logger.info('Stopping recording with frames:', currentFrames.length);
451
474
  const result: VideoRecordingResult = {
452
- frames,
475
+ frames: currentFrames,
453
476
  duration: actualDuration,
454
477
  instructionsFollowed: completedChallenges.length === challenges.length,
455
- qualityScore: Math.min(100, (frames.length / 30) * 100),
478
+ qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
456
479
  challengesCompleted: completedChallenges,
457
480
  sessionId,
458
481
  };
459
482
  onComplete(result);
460
483
  } else {
484
+ logger.info('Stopping recording with frames (fallback):', currentFrames.length);
461
485
  const result: VideoRecordingResult = {
462
- frames: frames.length > 0 ? frames : [],
486
+ frames: currentFrames.length > 0 ? currentFrames : [],
463
487
  duration: actualDuration,
464
488
  instructionsFollowed: completedChallenges.length === challenges.length,
465
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 0,
489
+ qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
466
490
  challengesCompleted: completedChallenges,
467
491
  sessionId,
468
492
  };
@@ -471,9 +495,10 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
471
495
  }
472
496
  } else {
473
497
  const actualDuration = Date.now() - recordingStartTime.current;
498
+ const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
474
499
 
475
- if (frames.length === 0 && actualDuration < minDurationMs) {
476
- logger.error('No frames and duration too short, cannot complete');
500
+ if (currentFrames.length === 0 && actualDuration < minDurationMs) {
501
+ logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
477
502
  setPhase('recording');
478
503
  Alert.alert(
479
504
  strings.errors.videoTooShort?.title || 'Recording Too Short',
@@ -486,11 +511,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
486
511
  setPhase('processing');
487
512
  setOverallProgress(100);
488
513
 
514
+ logger.info('Completing recording with frames:', currentFrames.length);
489
515
  const result: VideoRecordingResult = {
490
- frames: frames.length > 0 ? frames : [],
516
+ frames: currentFrames.length > 0 ? currentFrames : [],
491
517
  duration: actualDuration,
492
518
  instructionsFollowed: completedChallenges.length === challenges.length,
493
- qualityScore: frames.length > 0 ? Math.min(100, (frames.length / 30) * 100) : 0,
519
+ qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
494
520
  challengesCompleted: completedChallenges,
495
521
  sessionId,
496
522
  };