@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.
- package/dist/components/BiometricIdentityFlow.d.ts.map +1 -1
- package/dist/components/BiometricIdentityFlow.js +16 -1
- package/dist/components/VideoRecorder.d.ts.map +1 -1
- package/dist/components/VideoRecorder.js +76 -33
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -1
- package/dist/hooks/useBiometricSDK.js +27 -4
- package/package.json +1 -1
- package/src/components/BiometricIdentityFlow.tsx +17 -1
- package/src/components/VideoRecorder.tsx +78 -42
- package/src/hooks/useBiometricSDK.ts +30 -4
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
278
|
+
console.log('handleVideoComplete called with video:', video?.path);
|
|
278
279
|
if (!isRecordingRef.current && phase !== 'processing') {
|
|
279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
299
|
+
console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
|
|
299
300
|
}
|
|
300
301
|
catch (fsError) {
|
|
301
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
395
|
+
console.log('Video recording stopped - waiting for onRecordingFinished callback');
|
|
379
396
|
}
|
|
380
397
|
catch (error) {
|
|
381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
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
|
-
|
|
525
|
+
console.error('Recording error:', error);
|
|
491
526
|
handleRecordingError(error);
|
|
492
527
|
},
|
|
493
528
|
});
|
|
494
|
-
|
|
529
|
+
console.log('Video recording started successfully');
|
|
495
530
|
}
|
|
496
531
|
catch (error) {
|
|
497
|
-
|
|
532
|
+
console.warn('Video recording not available, falling back to frame capture:', error);
|
|
498
533
|
startFrameCapture();
|
|
499
534
|
}
|
|
500
535
|
}
|
|
501
536
|
else {
|
|
502
|
-
|
|
537
|
+
console.log('Camera not available, using frame capture');
|
|
503
538
|
startFrameCapture();
|
|
504
539
|
}
|
|
505
540
|
runChallenge(0);
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
548
|
+
console.error('Error stopping recording on safety timeout:', err);
|
|
511
549
|
});
|
|
512
550
|
}
|
|
513
|
-
},
|
|
514
|
-
return () =>
|
|
515
|
-
|
|
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,
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
@@ -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
|
-
|
|
332
|
+
console.log('handleVideoComplete called with video:', video?.path);
|
|
332
333
|
|
|
333
334
|
if (!isRecordingRef.current && phase !== 'processing') {
|
|
334
|
-
|
|
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
|
-
|
|
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
|
-
|
|
362
|
+
console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
|
|
365
363
|
} catch (fsError) {
|
|
366
|
-
|
|
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
|
-
|
|
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
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
467
|
+
console.log('Video recording stopped - waiting for onRecordingFinished callback');
|
|
455
468
|
} catch (error) {
|
|
456
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
580
|
-
|
|
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
|
-
|
|
611
|
+
console.error('Recording error:', error);
|
|
584
612
|
handleRecordingError(error);
|
|
585
613
|
},
|
|
586
614
|
});
|
|
587
|
-
|
|
615
|
+
console.log('Video recording started successfully');
|
|
588
616
|
} catch (error) {
|
|
589
|
-
|
|
617
|
+
console.warn('Video recording not available, falling back to frame capture:', error);
|
|
590
618
|
startFrameCapture();
|
|
591
619
|
}
|
|
592
620
|
} else {
|
|
593
|
-
|
|
621
|
+
console.log('Camera not available, using frame capture');
|
|
594
622
|
startFrameCapture();
|
|
595
623
|
}
|
|
596
624
|
|
|
597
625
|
runChallenge(0);
|
|
598
626
|
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
634
|
+
console.error('Error stopping recording on safety timeout:', err);
|
|
604
635
|
});
|
|
605
636
|
}
|
|
606
|
-
},
|
|
637
|
+
}, maxDuration);
|
|
607
638
|
|
|
608
|
-
return () =>
|
|
609
|
-
|
|
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
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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
|
/**
|