@hexar/biometric-identity-sdk-react-native 1.1.18 → 1.1.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":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AASxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,cAAc,EAAsB,MAAM,oCAAoC,CAAC;AAGzJ,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAiPtE,CAAC;AAuBF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"ProfilePictureCapture.d.ts","sourceRoot":"","sources":["../../src/components/ProfilePictureCapture.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AASxE,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,cAAc,EAAsB,MAAM,oCAAoC,CAAC;AAGzJ,MAAM,WAAW,8BAA8B;IAC7C,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,CAAC,MAAM,EAAE,8BAA8B,KAAK,IAAI,CAAC;IAC7D,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAqOtE,CAAC;AAuBF,eAAe,qBAAqB,CAAC"}
@@ -105,17 +105,6 @@ const ProfilePictureCapture = ({ onComplete, onError, onCancel, theme, language,
105
105
  biometric_identity_sdk_core_1.logger.error('Session ID not available after attempting to generate');
106
106
  throw new Error('VALIDATION_ERROR');
107
107
  }
108
- biometric_identity_sdk_core_1.logger.info('Validating profile picture with backend', {
109
- sessionId,
110
- framesCount: videoResult.frames.length,
111
- duration: videoResult.duration,
112
- isUsingBackend,
113
- hasVideoFrames: !!videoResult.frames,
114
- videoFramesType: typeof videoResult.frames,
115
- firstFrameLength: videoResult.frames?.[0]?.length || 0,
116
- challengesCompleted: videoResult.challengesCompleted || [],
117
- challengesCompletedCount: videoResult.challengesCompleted?.length || 0,
118
- });
119
108
  if (!videoResult.frames || videoResult.frames.length === 0) {
120
109
  throw new Error('No video frames available for validation');
121
110
  }
@@ -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;AAiDD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAg5BtD,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;AAiDD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAg6BtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
@@ -102,6 +102,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
102
102
  const [challengeProgress, setChallengeProgress] = (0, react_1.useState)(0);
103
103
  const [overallProgress, setOverallProgress] = (0, react_1.useState)(0);
104
104
  const [completedChallenges, setCompletedChallenges] = (0, react_1.useState)([]);
105
+ const completedChallengesRef = (0, react_1.useRef)([]);
105
106
  const [frames, setFrames] = (0, react_1.useState)([]);
106
107
  const framesRef = (0, react_1.useRef)([]);
107
108
  const [hasPermission, setHasPermission] = (0, react_1.useState)(false);
@@ -158,14 +159,6 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
158
159
  let challengeList;
159
160
  const currentStrings = (0, biometric_identity_sdk_core_1.getStrings)();
160
161
  const instructionMap = getInstructionMap(currentStrings);
161
- biometric_identity_sdk_core_1.logger.info('Initializing challenges', {
162
- language,
163
- hasStrings: !!currentStrings,
164
- hasInstructions: !!currentStrings?.liveness?.instructions,
165
- instructionMapKeys: Object.keys(instructionMap),
166
- turnLeftTranslation: instructionMap['turn_left']?.text,
167
- turnRightTranslation: instructionMap['turn_right']?.text
168
- });
169
162
  if (propChallenges && propChallenges.length > 0) {
170
163
  challengeList = propChallenges.map(challenge => ({
171
164
  ...challenge,
@@ -280,6 +273,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
280
273
  setFrames([]);
281
274
  framesRef.current = [];
282
275
  setCompletedChallenges([]);
276
+ completedChallengesRef.current = [];
283
277
  setCurrentChallengeIndex(0);
284
278
  setChallengeProgress(0);
285
279
  setOverallProgress(0);
@@ -327,15 +321,22 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
327
321
  return;
328
322
  }
329
323
  biometric_identity_sdk_core_1.logger.info('Using frames for completion:', finalFrames.length, 'frames');
324
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
325
+ biometric_identity_sdk_core_1.logger.info('VideoRecorder: handleVideoComplete - completed challenges', {
326
+ refCount: completedChallengesRef.current.length,
327
+ stateCount: completedChallenges.length,
328
+ usingRef: completedChallengesRef.current.length > 0,
329
+ challengesCompleted: currentCompletedChallenges
330
+ });
330
331
  const result = {
331
332
  frames: finalFrames,
332
333
  duration: actualDuration,
333
- instructionsFollowed: completedChallenges.length === challenges.length,
334
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
334
335
  qualityScore: finalFrames.length > 0 ? Math.min(100, (finalFrames.length / 30) * 100) : 85,
335
- challengesCompleted: completedChallenges,
336
+ challengesCompleted: currentCompletedChallenges,
336
337
  sessionId,
337
338
  };
338
- biometric_identity_sdk_core_1.logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's');
339
+ biometric_identity_sdk_core_1.logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's', 'challenges:', currentCompletedChallenges.length);
339
340
  isRecordingRef.current = false;
340
341
  onComplete(result);
341
342
  }
@@ -449,14 +450,15 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
449
450
  biometric_identity_sdk_core_1.logger.error('Error stopping video recording:', error);
450
451
  const actualDuration = Date.now() - recordingStartTime.current;
451
452
  const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
453
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
452
454
  if (actualDuration >= minDurationMs && currentFrames.length > 0) {
453
455
  biometric_identity_sdk_core_1.logger.info('Stopping recording with frames:', currentFrames.length);
454
456
  const result = {
455
457
  frames: currentFrames,
456
458
  duration: actualDuration,
457
- instructionsFollowed: completedChallenges.length === challenges.length,
459
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
458
460
  qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
459
- challengesCompleted: completedChallenges,
461
+ challengesCompleted: currentCompletedChallenges,
460
462
  sessionId,
461
463
  };
462
464
  onComplete(result);
@@ -466,9 +468,9 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
466
468
  const result = {
467
469
  frames: currentFrames.length > 0 ? currentFrames : [],
468
470
  duration: actualDuration,
469
- instructionsFollowed: completedChallenges.length === challenges.length,
471
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
470
472
  qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
471
- challengesCompleted: completedChallenges,
473
+ challengesCompleted: currentCompletedChallenges,
472
474
  sessionId,
473
475
  };
474
476
  onComplete(result);
@@ -486,13 +488,20 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
486
488
  }
487
489
  setPhase('processing');
488
490
  setOverallProgress(100);
491
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
492
+ biometric_identity_sdk_core_1.logger.info('VideoRecorder: stopRecording - completed challenges', {
493
+ refCount: completedChallengesRef.current.length,
494
+ stateCount: completedChallenges.length,
495
+ usingRef: completedChallengesRef.current.length > 0,
496
+ challengesCompleted: currentCompletedChallenges
497
+ });
489
498
  biometric_identity_sdk_core_1.logger.info('Completing recording with frames:', currentFrames.length);
490
499
  const result = {
491
500
  frames: currentFrames.length > 0 ? currentFrames : [],
492
501
  duration: actualDuration,
493
- instructionsFollowed: completedChallenges.length === challenges.length,
502
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
494
503
  qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
495
- challengesCompleted: completedChallenges,
504
+ challengesCompleted: currentCompletedChallenges,
496
505
  sessionId,
497
506
  };
498
507
  setTimeout(() => {
@@ -501,11 +510,12 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
501
510
  }
502
511
  }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
503
512
  const runChallenge = (0, react_1.useCallback)((index) => {
513
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
504
514
  biometric_identity_sdk_core_1.logger.info('VideoRecorder: runChallenge called', {
505
515
  index,
506
516
  challengesLength: challenges.length,
507
517
  challengeActions: challenges.map(c => c.action),
508
- completedChallengesCount: completedChallenges.length
518
+ completedChallengesCount: currentCompletedChallenges.length
509
519
  });
510
520
  if (index >= challenges.length) {
511
521
  setOverallProgress(100);
@@ -528,13 +538,14 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
528
538
  }
529
539
  else {
530
540
  const actualDuration = Date.now() - recordingStartTime.current;
541
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
531
542
  if (actualDuration >= minDurationMs && frames.length > 0) {
532
543
  const result = {
533
544
  frames,
534
545
  duration: actualDuration,
535
- instructionsFollowed: completedChallenges.length === challenges.length,
546
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
536
547
  qualityScore: Math.min(100, (frames.length / 30) * 100),
537
- challengesCompleted: completedChallenges,
548
+ challengesCompleted: currentCompletedChallenges,
538
549
  sessionId,
539
550
  };
540
551
  onComplete(result);
@@ -571,6 +582,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
571
582
  clearInterval(progressInterval);
572
583
  setCompletedChallenges(prev => {
573
584
  const updated = [...prev, challenge.action];
585
+ completedChallengesRef.current = updated; // Keep ref in sync
574
586
  biometric_identity_sdk_core_1.logger.info('VideoRecorder: Challenge completed', {
575
587
  action: challenge.action,
576
588
  index,
@@ -593,6 +605,9 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
593
605
  setPhase('recording');
594
606
  recordingStartTime.current = Date.now();
595
607
  isRecordingRef.current = true;
608
+ // Reset completed challenges ref when starting new recording
609
+ completedChallengesRef.current = [];
610
+ setCompletedChallenges([]);
596
611
  if (cameraRef.current && device) {
597
612
  try {
598
613
  videoRecordingRef.current = await cameraRef.current.startRecording({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hexar/biometric-identity-sdk-react-native",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "description": "React Native wrapper for Biometric Identity SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -112,18 +112,6 @@ export const ProfilePictureCapture: React.FC<ProfilePictureCaptureProps> = ({
112
112
  throw new Error('VALIDATION_ERROR');
113
113
  }
114
114
 
115
- logger.info('Validating profile picture with backend', {
116
- sessionId,
117
- framesCount: videoResult.frames.length,
118
- duration: videoResult.duration,
119
- isUsingBackend,
120
- hasVideoFrames: !!videoResult.frames,
121
- videoFramesType: typeof videoResult.frames,
122
- firstFrameLength: videoResult.frames?.[0]?.length || 0,
123
- challengesCompleted: videoResult.challengesCompleted || [],
124
- challengesCompletedCount: videoResult.challengesCompleted?.length || 0,
125
- });
126
-
127
115
  if (!videoResult.frames || videoResult.frames.length === 0) {
128
116
  throw new Error('No video frames available for validation');
129
117
  }
@@ -129,6 +129,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
129
129
  const [challengeProgress, setChallengeProgress] = useState(0);
130
130
  const [overallProgress, setOverallProgress] = useState(0);
131
131
  const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
132
+ const completedChallengesRef = useRef<string[]>([]);
132
133
  const [frames, setFrames] = useState<string[]>([]);
133
134
  const framesRef = useRef<string[]>([]);
134
135
  const [hasPermission, setHasPermission] = useState(false);
@@ -197,15 +198,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
197
198
  let challengeList: ChallengeAction[];
198
199
  const currentStrings = getStrings();
199
200
  const instructionMap = getInstructionMap(currentStrings);
200
- logger.info('Initializing challenges', {
201
- language,
202
- hasStrings: !!currentStrings,
203
- hasInstructions: !!currentStrings?.liveness?.instructions,
204
- instructionMapKeys: Object.keys(instructionMap),
205
- turnLeftTranslation: instructionMap['turn_left']?.text,
206
- turnRightTranslation: instructionMap['turn_right']?.text
207
- });
208
-
201
+
209
202
  if (propChallenges && propChallenges.length > 0) {
210
203
  challengeList = propChallenges.map(challenge => ({
211
204
  ...challenge,
@@ -327,6 +320,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
327
320
  setFrames([]);
328
321
  framesRef.current = [];
329
322
  setCompletedChallenges([]);
323
+ completedChallengesRef.current = [];
330
324
  setCurrentChallengeIndex(0);
331
325
  setChallengeProgress(0);
332
326
  setOverallProgress(0);
@@ -392,16 +386,24 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
392
386
 
393
387
  logger.info('Using frames for completion:', finalFrames.length, 'frames');
394
388
 
389
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
390
+ logger.info('VideoRecorder: handleVideoComplete - completed challenges', {
391
+ refCount: completedChallengesRef.current.length,
392
+ stateCount: completedChallenges.length,
393
+ usingRef: completedChallengesRef.current.length > 0,
394
+ challengesCompleted: currentCompletedChallenges
395
+ });
396
+
395
397
  const result: VideoRecordingResult = {
396
398
  frames: finalFrames,
397
399
  duration: actualDuration,
398
- instructionsFollowed: completedChallenges.length === challenges.length,
400
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
399
401
  qualityScore: finalFrames.length > 0 ? Math.min(100, (finalFrames.length / 30) * 100) : 85,
400
- challengesCompleted: completedChallenges,
402
+ challengesCompleted: currentCompletedChallenges,
401
403
  sessionId,
402
404
  };
403
405
 
404
- logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's');
406
+ logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's', 'challenges:', currentCompletedChallenges.length);
405
407
 
406
408
  isRecordingRef.current = false;
407
409
  onComplete(result);
@@ -522,14 +524,15 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
522
524
  const actualDuration = Date.now() - recordingStartTime.current;
523
525
  const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
524
526
 
527
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
525
528
  if (actualDuration >= minDurationMs && currentFrames.length > 0) {
526
529
  logger.info('Stopping recording with frames:', currentFrames.length);
527
530
  const result: VideoRecordingResult = {
528
531
  frames: currentFrames,
529
532
  duration: actualDuration,
530
- instructionsFollowed: completedChallenges.length === challenges.length,
533
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
531
534
  qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
532
- challengesCompleted: completedChallenges,
535
+ challengesCompleted: currentCompletedChallenges,
533
536
  sessionId,
534
537
  };
535
538
  onComplete(result);
@@ -538,9 +541,9 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
538
541
  const result: VideoRecordingResult = {
539
542
  frames: currentFrames.length > 0 ? currentFrames : [],
540
543
  duration: actualDuration,
541
- instructionsFollowed: completedChallenges.length === challenges.length,
544
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
542
545
  qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
543
- challengesCompleted: completedChallenges,
546
+ challengesCompleted: currentCompletedChallenges,
544
547
  sessionId,
545
548
  };
546
549
  onComplete(result);
@@ -564,13 +567,20 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
564
567
  setPhase('processing');
565
568
  setOverallProgress(100);
566
569
 
570
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
571
+ logger.info('VideoRecorder: stopRecording - completed challenges', {
572
+ refCount: completedChallengesRef.current.length,
573
+ stateCount: completedChallenges.length,
574
+ usingRef: completedChallengesRef.current.length > 0,
575
+ challengesCompleted: currentCompletedChallenges
576
+ });
567
577
  logger.info('Completing recording with frames:', currentFrames.length);
568
578
  const result: VideoRecordingResult = {
569
579
  frames: currentFrames.length > 0 ? currentFrames : [],
570
580
  duration: actualDuration,
571
- instructionsFollowed: completedChallenges.length === challenges.length,
581
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
572
582
  qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
573
- challengesCompleted: completedChallenges,
583
+ challengesCompleted: currentCompletedChallenges,
574
584
  sessionId,
575
585
  };
576
586
 
@@ -581,11 +591,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
581
591
  }, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
582
592
 
583
593
  const runChallenge = useCallback((index: number) => {
594
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
584
595
  logger.info('VideoRecorder: runChallenge called', {
585
596
  index,
586
597
  challengesLength: challenges.length,
587
598
  challengeActions: challenges.map(c => c.action),
588
- completedChallengesCount: completedChallenges.length
599
+ completedChallengesCount: currentCompletedChallenges.length
589
600
  });
590
601
 
591
602
  if (index >= challenges.length) {
@@ -612,13 +623,14 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
612
623
  stopRecording();
613
624
  } else {
614
625
  const actualDuration = Date.now() - recordingStartTime.current;
626
+ const currentCompletedChallenges = completedChallengesRef.current.length > 0 ? completedChallengesRef.current : completedChallenges;
615
627
  if (actualDuration >= minDurationMs && frames.length > 0) {
616
628
  const result: VideoRecordingResult = {
617
629
  frames,
618
630
  duration: actualDuration,
619
- instructionsFollowed: completedChallenges.length === challenges.length,
631
+ instructionsFollowed: currentCompletedChallenges.length === challenges.length,
620
632
  qualityScore: Math.min(100, (frames.length / 30) * 100),
621
- challengesCompleted: completedChallenges,
633
+ challengesCompleted: currentCompletedChallenges,
622
634
  sessionId,
623
635
  };
624
636
  onComplete(result);
@@ -663,6 +675,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
663
675
 
664
676
  setCompletedChallenges(prev => {
665
677
  const updated = [...prev, challenge.action];
678
+ completedChallengesRef.current = updated; // Keep ref in sync
666
679
  logger.info('VideoRecorder: Challenge completed', {
667
680
  action: challenge.action,
668
681
  index,
@@ -687,6 +700,9 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
687
700
  setPhase('recording');
688
701
  recordingStartTime.current = Date.now();
689
702
  isRecordingRef.current = true;
703
+ // Reset completed challenges ref when starting new recording
704
+ completedChallengesRef.current = [];
705
+ setCompletedChallenges([]);
690
706
 
691
707
  if (cameraRef.current && device) {
692
708
  try {