@hexar/biometric-identity-sdk-react-native 1.0.12 → 1.0.14
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 +4 -20
- package/dist/components/CameraCapture.d.ts.map +1 -1
- package/dist/components/CameraCapture.js +3 -3
- package/dist/components/VideoRecorder.d.ts.map +1 -1
- package/dist/components/VideoRecorder.js +25 -72
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -1
- package/dist/hooks/useBiometricSDK.js +4 -11
- package/package.json +1 -1
- package/src/components/BiometricIdentityFlow.tsx +5 -21
- package/src/components/CameraCapture.tsx +4 -4
- package/src/components/VideoRecorder.tsx +33 -77
- package/src/hooks/useBiometricSDK.ts +5 -12
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAA2C,MAAM,OAAO,CAAC;AAChE,OAAO,EAOL,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,gBAAgB,EAChB,WAAW,EACX,cAAc,EAKd,iBAAiB,EAElB,MAAM,oCAAoC,CAAC;AAU5C,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACzD,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,SAAS,CAAC;QACtB,OAAO,CAAC,EAAE,SAAS,CAAC;KACrB,CAAC;CACH;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAgTtE,CAAC;AAiOF,eAAe,qBAAqB,CAAC"}
|
|
@@ -94,7 +94,7 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
94
94
|
setCurrentChallenges(challenges);
|
|
95
95
|
}
|
|
96
96
|
catch (error) {
|
|
97
|
-
|
|
97
|
+
biometric_identity_sdk_core_1.logger.warn('Failed to fetch challenges, using defaults');
|
|
98
98
|
setCurrentChallenges(sdk.getDefaultChallenges());
|
|
99
99
|
}
|
|
100
100
|
setIsLoadingChallenges(false);
|
|
@@ -109,28 +109,16 @@ 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);
|
|
113
112
|
setShowCamera(false);
|
|
114
113
|
try {
|
|
115
114
|
if (cameraMode === 'front') {
|
|
116
|
-
console.log('Uploading front ID');
|
|
117
115
|
await uploadFrontID(data);
|
|
118
|
-
console.log('Front ID uploaded successfully');
|
|
119
116
|
}
|
|
120
117
|
else if (cameraMode === 'back') {
|
|
121
|
-
console.log('Uploading back ID');
|
|
122
118
|
await uploadBackID(data);
|
|
123
|
-
console.log('Back ID uploaded successfully');
|
|
124
119
|
}
|
|
125
120
|
else if (cameraMode === 'video') {
|
|
126
|
-
console.log('Processing video recording result');
|
|
127
|
-
// Handle video recording result
|
|
128
121
|
const videoResult = data;
|
|
129
|
-
console.log('Storing video recording:', {
|
|
130
|
-
frames: videoResult.frames.length,
|
|
131
|
-
duration: videoResult.duration,
|
|
132
|
-
challengesCompleted: videoResult.challengesCompleted.length
|
|
133
|
-
});
|
|
134
122
|
await storeVideoRecording({
|
|
135
123
|
frames: videoResult.frames,
|
|
136
124
|
duration: videoResult.duration,
|
|
@@ -139,14 +127,12 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
139
127
|
challengesCompleted: videoResult.challengesCompleted,
|
|
140
128
|
sessionId: videoResult.sessionId,
|
|
141
129
|
});
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const result = await validateIdentity();
|
|
145
|
-
console.log('Validation complete, result:', result);
|
|
130
|
+
biometric_identity_sdk_core_1.logger.info('Starting validation...');
|
|
131
|
+
await validateIdentity();
|
|
146
132
|
}
|
|
147
133
|
}
|
|
148
134
|
catch (error) {
|
|
149
|
-
|
|
135
|
+
biometric_identity_sdk_core_1.logger.error('Capture error:', error);
|
|
150
136
|
onError({
|
|
151
137
|
name: 'BiometricError',
|
|
152
138
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
@@ -185,12 +171,10 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
185
171
|
}
|
|
186
172
|
// Show validation progress
|
|
187
173
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.VALIDATING) {
|
|
188
|
-
console.log('Rendering ValidationProgress, progress:', state.progress);
|
|
189
174
|
return (react_1.default.createElement(ValidationProgress_1.ValidationProgress, { progress: state.progress, theme: theme, language: language }));
|
|
190
175
|
}
|
|
191
176
|
// Show result
|
|
192
177
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult) {
|
|
193
|
-
console.log('Rendering ResultScreen, result:', state.validationResult);
|
|
194
178
|
return (react_1.default.createElement(ResultScreen_1.ResultScreen, { result: state.validationResult, theme: theme, language: language, onClose: () => onValidationComplete(state.validationResult) }));
|
|
195
179
|
}
|
|
196
180
|
// Show error
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAY3D,OAAO,EAAE,WAAW,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"CameraCapture.d.ts","sourceRoot":"","sources":["../../src/components/CameraCapture.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAsC,MAAM,OAAO,CAAC;AAY3D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAIrH,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CA6NtD,CAAC;AA0JF,eAAe,aAAa,CAAC"}
|
|
@@ -81,7 +81,7 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
|
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
catch (error) {
|
|
84
|
-
|
|
84
|
+
biometric_identity_sdk_core_1.logger.error('Permission check error:', error);
|
|
85
85
|
setHasPermission(false);
|
|
86
86
|
const errorMsg = typeof strings.errors.cameraPermissionDenied === 'string'
|
|
87
87
|
? strings.errors.cameraPermissionDenied
|
|
@@ -131,14 +131,14 @@ const CameraCapture = ({ mode, theme, language, onCapture, onCancel, }) => {
|
|
|
131
131
|
}
|
|
132
132
|
catch (fetchError) {
|
|
133
133
|
// Final fallback: pass file path (SDK should handle file paths)
|
|
134
|
-
|
|
134
|
+
biometric_identity_sdk_core_1.logger.warn('Could not convert to base64, using file path:', photo.path);
|
|
135
135
|
onCapture(photo.path);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
setIsCapturing(false);
|
|
139
139
|
}
|
|
140
140
|
catch (error) {
|
|
141
|
-
|
|
141
|
+
biometric_identity_sdk_core_1.logger.error('Capture error:', error);
|
|
142
142
|
react_native_1.Alert.alert('Capture Failed', 'Could not capture photo. Please try again.');
|
|
143
143
|
setIsCapturing(false);
|
|
144
144
|
}
|
|
@@ -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,
|
|
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;AAG1I,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,CAkyBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
|
|
@@ -144,7 +144,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
catch (error) {
|
|
147
|
-
|
|
147
|
+
biometric_identity_sdk_core_1.logger.error('Permission check error:', error);
|
|
148
148
|
setHasPermission(false);
|
|
149
149
|
react_native_1.Alert.alert('Camera Permission Required', 'Please enable camera access in your device settings to record your face video.');
|
|
150
150
|
}
|
|
@@ -190,7 +190,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
190
190
|
setPhase('countdown');
|
|
191
191
|
}
|
|
192
192
|
catch (error) {
|
|
193
|
-
|
|
193
|
+
biometric_identity_sdk_core_1.logger.error('Failed to fetch challenges:', error);
|
|
194
194
|
// Fallback to default
|
|
195
195
|
setChallenges(DEFAULT_CHALLENGES);
|
|
196
196
|
setPhase('countdown');
|
|
@@ -275,18 +275,13 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
275
275
|
react_native_1.Alert.alert('Recording Error', 'Failed to record video. Please try again.', [{ text: 'OK', onPress: onCancel }]);
|
|
276
276
|
}, [onCancel]);
|
|
277
277
|
const handleVideoComplete = (0, react_1.useCallback)(async (video) => {
|
|
278
|
-
console.log('handleVideoComplete called with video:', video?.path, 'current phase:', phase, 'isRecording:', isRecordingRef.current);
|
|
279
|
-
// Don't process if we're already done (result screen or error)
|
|
280
278
|
if (phase === 'loading' && !video) {
|
|
281
|
-
console.log('Video completion in loading phase with no video, ignoring');
|
|
282
279
|
return;
|
|
283
280
|
}
|
|
284
281
|
try {
|
|
285
|
-
console.log('Setting phase to processing');
|
|
286
282
|
setPhase('processing');
|
|
287
283
|
setOverallProgress(100);
|
|
288
284
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
289
|
-
console.log('Video Processing - Duration:', (actualDuration / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's, Frames:', frames.length);
|
|
290
285
|
if (actualDuration < minDurationMs) {
|
|
291
286
|
setPhase('recording');
|
|
292
287
|
setOverallProgress(0);
|
|
@@ -298,15 +293,17 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
298
293
|
try {
|
|
299
294
|
const RNFS = require('react-native-fs');
|
|
300
295
|
videoBase64 = await RNFS.readFile(video.path, 'base64');
|
|
301
|
-
console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
|
|
302
296
|
}
|
|
303
297
|
catch (fsError) {
|
|
304
|
-
|
|
298
|
+
biometric_identity_sdk_core_1.logger.warn('Could not read video file, using captured frames');
|
|
305
299
|
}
|
|
306
300
|
}
|
|
307
|
-
|
|
301
|
+
let finalFrames = frames.length > 0 ? frames : [];
|
|
302
|
+
if (finalFrames.length === 0 && videoBase64) {
|
|
303
|
+
finalFrames = [videoBase64];
|
|
304
|
+
}
|
|
308
305
|
if (finalFrames.length === 0) {
|
|
309
|
-
|
|
306
|
+
biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete');
|
|
310
307
|
setPhase('recording');
|
|
311
308
|
handleRecordingError(new Error('No video frames captured'));
|
|
312
309
|
return;
|
|
@@ -319,18 +316,12 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
319
316
|
challengesCompleted: completedChallenges,
|
|
320
317
|
sessionId,
|
|
321
318
|
};
|
|
322
|
-
|
|
323
|
-
duration: (actualDuration / 1000).toFixed(1) + 's',
|
|
324
|
-
frames: result.frames.length,
|
|
325
|
-
challenges: `${completedChallenges.length}/${challenges.length}`,
|
|
326
|
-
instructionsFollowed: result.instructionsFollowed,
|
|
327
|
-
quality: result.qualityScore.toFixed(0) + '%'
|
|
328
|
-
});
|
|
319
|
+
biometric_identity_sdk_core_1.logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's');
|
|
329
320
|
isRecordingRef.current = false;
|
|
330
321
|
onComplete(result);
|
|
331
322
|
}
|
|
332
323
|
catch (error) {
|
|
333
|
-
|
|
324
|
+
biometric_identity_sdk_core_1.logger.error('Error processing video:', error);
|
|
334
325
|
setPhase('recording');
|
|
335
326
|
setOverallProgress(0);
|
|
336
327
|
handleRecordingError(error);
|
|
@@ -338,7 +329,6 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
338
329
|
}, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
|
|
339
330
|
const startFrameCapture = (0, react_1.useCallback)(() => {
|
|
340
331
|
if (cameraRef.current && device) {
|
|
341
|
-
console.log('Starting frame capture mode');
|
|
342
332
|
frameCaptureInterval.current = setInterval(async () => {
|
|
343
333
|
try {
|
|
344
334
|
const photo = await cameraRef.current?.takePhoto({
|
|
@@ -366,14 +356,12 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
366
356
|
}
|
|
367
357
|
}
|
|
368
358
|
catch (error) {
|
|
369
|
-
|
|
359
|
+
// Silent frame capture errors
|
|
370
360
|
}
|
|
371
361
|
}, 100);
|
|
372
362
|
}
|
|
373
363
|
}, [device]);
|
|
374
364
|
const stopRecording = (0, react_1.useCallback)(async () => {
|
|
375
|
-
console.log('stopRecording called, isRecording:', isRecordingRef.current, 'hasVideoRef:', !!videoRecordingRef.current);
|
|
376
|
-
// Clear any pending timeouts
|
|
377
365
|
if (recordingTimeoutRef.current) {
|
|
378
366
|
clearTimeout(recordingTimeoutRef.current);
|
|
379
367
|
recordingTimeoutRef.current = null;
|
|
@@ -382,28 +370,20 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
382
370
|
clearInterval(frameCaptureInterval.current);
|
|
383
371
|
frameCaptureInterval.current = null;
|
|
384
372
|
}
|
|
385
|
-
// Prevent multiple calls
|
|
386
373
|
if (!isRecordingRef.current) {
|
|
387
|
-
console.log('Recording already stopped, ignoring');
|
|
388
374
|
return;
|
|
389
375
|
}
|
|
390
|
-
// Mark as not recording first to prevent re-entry
|
|
391
376
|
isRecordingRef.current = false;
|
|
392
377
|
if (videoRecordingRef.current) {
|
|
393
378
|
try {
|
|
394
|
-
console.log('Stopping video recording, current ref exists');
|
|
395
379
|
const recording = videoRecordingRef.current;
|
|
396
380
|
videoRecordingRef.current = null;
|
|
397
381
|
await recording.stop();
|
|
398
|
-
console.log('Video recording stopped - waiting for onRecordingFinished callback');
|
|
399
|
-
// onRecordingFinished callback will handle completion
|
|
400
382
|
}
|
|
401
383
|
catch (error) {
|
|
402
|
-
|
|
384
|
+
biometric_identity_sdk_core_1.logger.error('Error stopping video recording:', error);
|
|
403
385
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
404
|
-
console.log('Error duration check:', actualDuration, 'minDurationMs:', minDurationMs, 'frames:', frames.length);
|
|
405
386
|
if (actualDuration >= minDurationMs && frames.length > 0) {
|
|
406
|
-
console.log('Video stopped with error, using captured frames');
|
|
407
387
|
const result = {
|
|
408
388
|
frames,
|
|
409
389
|
duration: actualDuration,
|
|
@@ -415,8 +395,6 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
415
395
|
onComplete(result);
|
|
416
396
|
}
|
|
417
397
|
else {
|
|
418
|
-
// Even if we don't have enough frames, try to complete with what we have
|
|
419
|
-
console.log('Completing with available data despite error');
|
|
420
398
|
const result = {
|
|
421
399
|
frames: frames.length > 0 ? frames : [],
|
|
422
400
|
duration: actualDuration,
|
|
@@ -430,12 +408,15 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
430
408
|
}
|
|
431
409
|
}
|
|
432
410
|
else {
|
|
433
|
-
console.log('No video recording ref - completing with captured frames');
|
|
434
411
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
435
|
-
|
|
412
|
+
if (frames.length === 0 && actualDuration < minDurationMs) {
|
|
413
|
+
biometric_identity_sdk_core_1.logger.error('No frames and duration too short, cannot complete');
|
|
414
|
+
setPhase('recording');
|
|
415
|
+
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 }]);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
436
418
|
setPhase('processing');
|
|
437
419
|
setOverallProgress(100);
|
|
438
|
-
// Complete with frames we have
|
|
439
420
|
const result = {
|
|
440
421
|
frames: frames.length > 0 ? frames : [],
|
|
441
422
|
duration: actualDuration,
|
|
@@ -444,49 +425,34 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
444
425
|
challengesCompleted: completedChallenges,
|
|
445
426
|
sessionId,
|
|
446
427
|
};
|
|
447
|
-
console.log('Completing without video ref:', {
|
|
448
|
-
frames: result.frames.length,
|
|
449
|
-
duration: (actualDuration / 1000).toFixed(1) + 's',
|
|
450
|
-
challenges: completedChallenges.length
|
|
451
|
-
});
|
|
452
|
-
// Small delay to ensure UI updates
|
|
453
428
|
setTimeout(() => {
|
|
454
429
|
onComplete(result);
|
|
455
430
|
}, 100);
|
|
456
431
|
}
|
|
457
|
-
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
|
|
432
|
+
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
|
|
458
433
|
const runChallenge = (0, react_1.useCallback)((index) => {
|
|
459
434
|
if (index >= challenges.length) {
|
|
460
|
-
console.log('All challenges completed, checking duration');
|
|
461
435
|
setOverallProgress(100);
|
|
462
|
-
// Clear the totalDuration timeout since we're handling it manually
|
|
463
436
|
if (recordingTimeoutRef.current) {
|
|
464
437
|
clearTimeout(recordingTimeoutRef.current);
|
|
465
438
|
recordingTimeoutRef.current = null;
|
|
466
|
-
console.log('Cleared totalDuration timeout');
|
|
467
439
|
}
|
|
468
440
|
if (isRecordingRef.current) {
|
|
469
441
|
const elapsed = Date.now() - recordingStartTime.current;
|
|
470
|
-
console.log('Checking duration - Elapsed:', (elapsed / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's');
|
|
471
442
|
if (elapsed < minDurationMs) {
|
|
472
443
|
const remaining = minDurationMs - elapsed;
|
|
473
|
-
console.log('Waiting additional', (remaining / 1000).toFixed(1), 's to meet minimum duration');
|
|
474
444
|
recordingTimeoutRef.current = setTimeout(() => {
|
|
475
|
-
console.log('Minimum duration wait completed, stopping recording');
|
|
476
445
|
if (isRecordingRef.current) {
|
|
477
446
|
stopRecording();
|
|
478
447
|
}
|
|
479
448
|
}, remaining);
|
|
480
449
|
return;
|
|
481
450
|
}
|
|
482
|
-
console.log('Duration requirement met, stopping immediately');
|
|
483
451
|
stopRecording();
|
|
484
452
|
}
|
|
485
453
|
else {
|
|
486
|
-
console.log('Recording already stopped, checking if we can complete');
|
|
487
454
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
488
455
|
if (actualDuration >= minDurationMs && frames.length > 0) {
|
|
489
|
-
console.log('Recording already stopped, completing with frames');
|
|
490
456
|
const result = {
|
|
491
457
|
frames,
|
|
492
458
|
duration: actualDuration,
|
|
@@ -542,47 +508,34 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
542
508
|
setPhase('recording');
|
|
543
509
|
recordingStartTime.current = Date.now();
|
|
544
510
|
isRecordingRef.current = true;
|
|
545
|
-
console.log('Starting video recording, total duration:', (totalDuration / 1000).toFixed(1), 's');
|
|
546
511
|
if (cameraRef.current && device) {
|
|
547
512
|
try {
|
|
548
513
|
videoRecordingRef.current = await cameraRef.current.startRecording({
|
|
549
514
|
flash: 'off',
|
|
550
515
|
onRecordingFinished: (video) => {
|
|
551
|
-
|
|
552
|
-
// Ensure we're in the right state to process
|
|
553
|
-
if (phase === 'recording' || phase === 'processing') {
|
|
554
|
-
handleVideoComplete(video);
|
|
555
|
-
}
|
|
556
|
-
else {
|
|
557
|
-
console.warn('Received onRecordingFinished but phase is', phase, '- calling handleVideoComplete anyway');
|
|
558
|
-
handleVideoComplete(video);
|
|
559
|
-
}
|
|
516
|
+
handleVideoComplete(video);
|
|
560
517
|
},
|
|
561
518
|
onRecordingError: (error) => {
|
|
562
|
-
|
|
519
|
+
biometric_identity_sdk_core_1.logger.error('Recording error:', error);
|
|
563
520
|
handleRecordingError(error);
|
|
564
521
|
},
|
|
565
522
|
});
|
|
566
|
-
|
|
523
|
+
startFrameCapture();
|
|
567
524
|
}
|
|
568
525
|
catch (error) {
|
|
569
|
-
|
|
526
|
+
biometric_identity_sdk_core_1.logger.warn('Video recording not available, falling back to frame capture');
|
|
570
527
|
startFrameCapture();
|
|
571
528
|
}
|
|
572
529
|
}
|
|
573
530
|
else {
|
|
574
|
-
console.log('Camera not available, using frame capture');
|
|
575
531
|
startFrameCapture();
|
|
576
532
|
}
|
|
577
533
|
runChallenge(0);
|
|
578
|
-
|
|
579
|
-
// This will be cleared if challenges complete early
|
|
580
|
-
const maxDuration = Math.max(totalDuration, minDurationMs + 2000); // Add 2s buffer
|
|
534
|
+
const maxDuration = Math.max(totalDuration, minDurationMs + 2000);
|
|
581
535
|
recordingTimeoutRef.current = setTimeout(() => {
|
|
582
|
-
console.log('Safety timeout reached (max duration), stopping recording');
|
|
583
536
|
if (isRecordingRef.current) {
|
|
584
537
|
stopRecording().catch(err => {
|
|
585
|
-
|
|
538
|
+
biometric_identity_sdk_core_1.logger.error('Error stopping recording on safety timeout:', err);
|
|
586
539
|
});
|
|
587
540
|
}
|
|
588
541
|
}, maxDuration);
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"useBiometricSDK.d.ts","sourceRoot":"","sources":["../../src/hooks/useBiometricSDK.ts"],"names":[],"mappings":"AACA,OAAO,EACL,oBAAoB,EACpB,QAAQ,EACR,WAAW,EAEZ,MAAM,oCAAoC,CAAC;AAE5C,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,oBAAoB,CAAC;IAC1B,KAAK,EAAE,QAAQ,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,mBAAmB,EAAE,CAAC,SAAS,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,eAAe,EAAE,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,SAAS,KAAK,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IAC7E,gBAAgB,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;IACrC,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,WAAW,EAAE,CAAC,SAAS,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzD;AAED,eAAO,MAAM,eAAe,QAAO,qBAuNlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -20,7 +20,7 @@ const useBiometricSDK = () => {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
23
|
-
|
|
23
|
+
biometric_identity_sdk_core_1.logger.error('SDK initialization failed:', error);
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
26
|
init();
|
|
@@ -67,7 +67,7 @@ const useBiometricSDK = () => {
|
|
|
67
67
|
return defaultChallenges;
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
|
|
70
|
+
biometric_identity_sdk_core_1.logger.error('Failed to fetch challenges:', error);
|
|
71
71
|
const defaultChallenges = sdk.getDefaultChallenges().map(c => ({
|
|
72
72
|
action: c.action,
|
|
73
73
|
instruction: c.instruction,
|
|
@@ -137,24 +137,17 @@ const useBiometricSDK = () => {
|
|
|
137
137
|
* Validate identity with all collected data
|
|
138
138
|
*/
|
|
139
139
|
const validateIdentity = (0, react_1.useCallback)(async () => {
|
|
140
|
-
console.log('validateIdentity called, current state:', sdk.getState().currentStep);
|
|
141
|
-
// Update state immediately to show validation screen
|
|
142
140
|
setState(sdk.getState());
|
|
143
|
-
// Poll state during validation to catch intermediate updates
|
|
144
141
|
const pollInterval = setInterval(() => {
|
|
145
142
|
if (isMounted.current) {
|
|
146
|
-
|
|
147
|
-
setState(currentState);
|
|
148
|
-
console.log('State polled - step:', currentState.currentStep, 'progress:', currentState.progress);
|
|
143
|
+
setState(sdk.getState());
|
|
149
144
|
}
|
|
150
145
|
}, 200);
|
|
151
146
|
try {
|
|
152
147
|
const result = await sdk.validateIdentity();
|
|
153
148
|
clearInterval(pollInterval);
|
|
154
149
|
if (isMounted.current) {
|
|
155
|
-
|
|
156
|
-
setState(finalState);
|
|
157
|
-
console.log('Validation complete, final state:', finalState.currentStep, 'result:', result);
|
|
150
|
+
setState(sdk.getState());
|
|
158
151
|
}
|
|
159
152
|
return result;
|
|
160
153
|
}
|
package/package.json
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getStrings,
|
|
23
23
|
setLanguage,
|
|
24
24
|
SupportedLanguage,
|
|
25
|
+
logger,
|
|
25
26
|
} from '@hexar/biometric-identity-sdk-core';
|
|
26
27
|
import type { ChallengeAction } from './VideoRecorder';
|
|
27
28
|
import { useBiometricSDK } from '../hooks/useBiometricSDK';
|
|
@@ -119,7 +120,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
119
120
|
const challenges = await fetchChallenges('active');
|
|
120
121
|
setCurrentChallenges(challenges);
|
|
121
122
|
} catch (error) {
|
|
122
|
-
|
|
123
|
+
logger.warn('Failed to fetch challenges, using defaults');
|
|
123
124
|
setCurrentChallenges(sdk.getDefaultChallenges());
|
|
124
125
|
}
|
|
125
126
|
setIsLoadingChallenges(false);
|
|
@@ -135,29 +136,16 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
135
136
|
* Handle capture completion
|
|
136
137
|
*/
|
|
137
138
|
const handleCaptureComplete = useCallback(async (data: any) => {
|
|
138
|
-
console.log('handleCaptureComplete called, cameraMode:', cameraMode);
|
|
139
139
|
setShowCamera(false);
|
|
140
140
|
|
|
141
141
|
try {
|
|
142
142
|
if (cameraMode === 'front') {
|
|
143
|
-
console.log('Uploading front ID');
|
|
144
143
|
await uploadFrontID(data);
|
|
145
|
-
console.log('Front ID uploaded successfully');
|
|
146
144
|
} else if (cameraMode === 'back') {
|
|
147
|
-
console.log('Uploading back ID');
|
|
148
145
|
await uploadBackID(data);
|
|
149
|
-
console.log('Back ID uploaded successfully');
|
|
150
146
|
} else if (cameraMode === 'video') {
|
|
151
|
-
console.log('Processing video recording result');
|
|
152
|
-
// Handle video recording result
|
|
153
147
|
const videoResult: VideoRecordingResult = data;
|
|
154
148
|
|
|
155
|
-
console.log('Storing video recording:', {
|
|
156
|
-
frames: videoResult.frames.length,
|
|
157
|
-
duration: videoResult.duration,
|
|
158
|
-
challengesCompleted: videoResult.challengesCompleted.length
|
|
159
|
-
});
|
|
160
|
-
|
|
161
149
|
await storeVideoRecording({
|
|
162
150
|
frames: videoResult.frames,
|
|
163
151
|
duration: videoResult.duration,
|
|
@@ -167,13 +155,11 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
167
155
|
sessionId: videoResult.sessionId,
|
|
168
156
|
});
|
|
169
157
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const result = await validateIdentity();
|
|
173
|
-
console.log('Validation complete, result:', result);
|
|
158
|
+
logger.info('Starting validation...');
|
|
159
|
+
await validateIdentity();
|
|
174
160
|
}
|
|
175
161
|
} catch (error) {
|
|
176
|
-
|
|
162
|
+
logger.error('Capture error:', error);
|
|
177
163
|
onError({
|
|
178
164
|
name: 'BiometricError',
|
|
179
165
|
message: error instanceof Error ? error.message : 'Unknown error during capture',
|
|
@@ -249,7 +235,6 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
249
235
|
|
|
250
236
|
// Show validation progress
|
|
251
237
|
if (state.currentStep === SDKStep.VALIDATING) {
|
|
252
|
-
console.log('Rendering ValidationProgress, progress:', state.progress);
|
|
253
238
|
return (
|
|
254
239
|
<ValidationProgress
|
|
255
240
|
progress={state.progress}
|
|
@@ -261,7 +246,6 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
261
246
|
|
|
262
247
|
// Show result
|
|
263
248
|
if (state.currentStep === SDKStep.RESULT && state.validationResult) {
|
|
264
|
-
console.log('Rendering ResultScreen, result:', state.validationResult);
|
|
265
249
|
return (
|
|
266
250
|
<ResultScreen
|
|
267
251
|
result={state.validationResult}
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
} from 'react-native';
|
|
16
16
|
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
17
17
|
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
|
18
|
-
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
18
|
+
import { ThemeConfig, SupportedLanguage, getStrings, setLanguage, logger } from '@hexar/biometric-identity-sdk-core';
|
|
19
19
|
|
|
20
20
|
const { width, height } = Dimensions.get('window');
|
|
21
21
|
|
|
@@ -74,7 +74,7 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
} catch (error) {
|
|
77
|
-
|
|
77
|
+
logger.error('Permission check error:', error);
|
|
78
78
|
setHasPermission(false);
|
|
79
79
|
const errorMsg = typeof strings.errors.cameraPermissionDenied === 'string'
|
|
80
80
|
? strings.errors.cameraPermissionDenied
|
|
@@ -129,14 +129,14 @@ export const CameraCapture: React.FC<CameraCaptureProps> = ({
|
|
|
129
129
|
return; // Don't set isCapturing to false here, wait for reader
|
|
130
130
|
} catch (fetchError) {
|
|
131
131
|
// Final fallback: pass file path (SDK should handle file paths)
|
|
132
|
-
|
|
132
|
+
logger.warn('Could not convert to base64, using file path:', photo.path);
|
|
133
133
|
onCapture(photo.path);
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
setIsCapturing(false);
|
|
138
138
|
} catch (error) {
|
|
139
|
-
|
|
139
|
+
logger.error('Capture error:', error);
|
|
140
140
|
Alert.alert('Capture Failed', 'Could not capture photo. Please try again.');
|
|
141
141
|
setIsCapturing(false);
|
|
142
142
|
}
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from 'react-native';
|
|
17
17
|
import { Camera, useCameraDevice, useCameraPermission } from 'react-native-vision-camera';
|
|
18
18
|
import { request, PERMISSIONS, RESULTS } from 'react-native-permissions';
|
|
19
|
-
import { ThemeConfig, LivenessInstruction, SupportedLanguage, getStrings, setLanguage } from '@hexar/biometric-identity-sdk-core';
|
|
19
|
+
import { ThemeConfig, LivenessInstruction, SupportedLanguage, getStrings, setLanguage, logger } from '@hexar/biometric-identity-sdk-core';
|
|
20
20
|
|
|
21
21
|
// Challenge action configuration (matches backend response)
|
|
22
22
|
export interface ChallengeAction {
|
|
@@ -179,7 +179,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
} catch (error) {
|
|
182
|
-
|
|
182
|
+
logger.error('Permission check error:', error);
|
|
183
183
|
setHasPermission(false);
|
|
184
184
|
Alert.alert(
|
|
185
185
|
'Camera Permission Required',
|
|
@@ -228,7 +228,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
228
228
|
setChallenges(challengeList);
|
|
229
229
|
setPhase('countdown');
|
|
230
230
|
} catch (error) {
|
|
231
|
-
|
|
231
|
+
logger.error('Failed to fetch challenges:', error);
|
|
232
232
|
// Fallback to default
|
|
233
233
|
setChallenges(DEFAULT_CHALLENGES);
|
|
234
234
|
setPhase('countdown');
|
|
@@ -329,21 +329,15 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
329
329
|
}, [onCancel]);
|
|
330
330
|
|
|
331
331
|
const handleVideoComplete = useCallback(async (video: any) => {
|
|
332
|
-
console.log('handleVideoComplete called with video:', video?.path, 'current phase:', phase, 'isRecording:', isRecordingRef.current);
|
|
333
|
-
|
|
334
|
-
// Don't process if we're already done (result screen or error)
|
|
335
332
|
if (phase === 'loading' && !video) {
|
|
336
|
-
console.log('Video completion in loading phase with no video, ignoring');
|
|
337
333
|
return;
|
|
338
334
|
}
|
|
339
335
|
|
|
340
336
|
try {
|
|
341
|
-
console.log('Setting phase to processing');
|
|
342
337
|
setPhase('processing');
|
|
343
338
|
setOverallProgress(100);
|
|
344
339
|
|
|
345
340
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
346
|
-
console.log('Video Processing - Duration:', (actualDuration / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's, Frames:', frames.length);
|
|
347
341
|
|
|
348
342
|
if (actualDuration < minDurationMs) {
|
|
349
343
|
setPhase('recording');
|
|
@@ -361,16 +355,19 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
361
355
|
try {
|
|
362
356
|
const RNFS = require('react-native-fs');
|
|
363
357
|
videoBase64 = await RNFS.readFile(video.path, 'base64');
|
|
364
|
-
console.log('Video file read successfully, size:', videoBase64.length, 'bytes');
|
|
365
358
|
} catch (fsError) {
|
|
366
|
-
|
|
359
|
+
logger.warn('Could not read video file, using captured frames');
|
|
367
360
|
}
|
|
368
361
|
}
|
|
369
362
|
|
|
370
|
-
|
|
363
|
+
let finalFrames = frames.length > 0 ? frames : [];
|
|
364
|
+
|
|
365
|
+
if (finalFrames.length === 0 && videoBase64) {
|
|
366
|
+
finalFrames = [videoBase64];
|
|
367
|
+
}
|
|
371
368
|
|
|
372
369
|
if (finalFrames.length === 0) {
|
|
373
|
-
|
|
370
|
+
logger.error('No frames or video available, cannot complete');
|
|
374
371
|
setPhase('recording');
|
|
375
372
|
handleRecordingError(new Error('No video frames captured'));
|
|
376
373
|
return;
|
|
@@ -385,18 +382,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
385
382
|
sessionId,
|
|
386
383
|
};
|
|
387
384
|
|
|
388
|
-
|
|
389
|
-
duration: (actualDuration / 1000).toFixed(1) + 's',
|
|
390
|
-
frames: result.frames.length,
|
|
391
|
-
challenges: `${completedChallenges.length}/${challenges.length}`,
|
|
392
|
-
instructionsFollowed: result.instructionsFollowed,
|
|
393
|
-
quality: result.qualityScore.toFixed(0) + '%'
|
|
394
|
-
});
|
|
385
|
+
logger.info('Video recording completed:', result.frames.length, 'frames,', (actualDuration / 1000).toFixed(1) + 's');
|
|
395
386
|
|
|
396
387
|
isRecordingRef.current = false;
|
|
397
388
|
onComplete(result);
|
|
398
389
|
} catch (error) {
|
|
399
|
-
|
|
390
|
+
logger.error('Error processing video:', error);
|
|
400
391
|
setPhase('recording');
|
|
401
392
|
setOverallProgress(0);
|
|
402
393
|
handleRecordingError(error);
|
|
@@ -405,7 +396,6 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
405
396
|
|
|
406
397
|
const startFrameCapture = useCallback(() => {
|
|
407
398
|
if (cameraRef.current && device) {
|
|
408
|
-
console.log('Starting frame capture mode');
|
|
409
399
|
frameCaptureInterval.current = setInterval(async () => {
|
|
410
400
|
try {
|
|
411
401
|
const photo = await cameraRef.current?.takePhoto({
|
|
@@ -432,16 +422,13 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
432
422
|
}
|
|
433
423
|
}
|
|
434
424
|
} catch (error) {
|
|
435
|
-
|
|
425
|
+
// Silent frame capture errors
|
|
436
426
|
}
|
|
437
427
|
}, 100);
|
|
438
428
|
}
|
|
439
429
|
}, [device]);
|
|
440
430
|
|
|
441
431
|
const stopRecording = useCallback(async () => {
|
|
442
|
-
console.log('stopRecording called, isRecording:', isRecordingRef.current, 'hasVideoRef:', !!videoRecordingRef.current);
|
|
443
|
-
|
|
444
|
-
// Clear any pending timeouts
|
|
445
432
|
if (recordingTimeoutRef.current) {
|
|
446
433
|
clearTimeout(recordingTimeoutRef.current);
|
|
447
434
|
recordingTimeoutRef.current = null;
|
|
@@ -452,31 +439,22 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
452
439
|
frameCaptureInterval.current = null;
|
|
453
440
|
}
|
|
454
441
|
|
|
455
|
-
// Prevent multiple calls
|
|
456
442
|
if (!isRecordingRef.current) {
|
|
457
|
-
console.log('Recording already stopped, ignoring');
|
|
458
443
|
return;
|
|
459
444
|
}
|
|
460
445
|
|
|
461
|
-
// Mark as not recording first to prevent re-entry
|
|
462
446
|
isRecordingRef.current = false;
|
|
463
447
|
|
|
464
448
|
if (videoRecordingRef.current) {
|
|
465
449
|
try {
|
|
466
|
-
console.log('Stopping video recording, current ref exists');
|
|
467
450
|
const recording = videoRecordingRef.current;
|
|
468
451
|
videoRecordingRef.current = null;
|
|
469
|
-
|
|
470
452
|
await recording.stop();
|
|
471
|
-
console.log('Video recording stopped - waiting for onRecordingFinished callback');
|
|
472
|
-
// onRecordingFinished callback will handle completion
|
|
473
453
|
} catch (error) {
|
|
474
|
-
|
|
454
|
+
logger.error('Error stopping video recording:', error);
|
|
475
455
|
|
|
476
456
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
477
|
-
console.log('Error duration check:', actualDuration, 'minDurationMs:', minDurationMs, 'frames:', frames.length);
|
|
478
457
|
if (actualDuration >= minDurationMs && frames.length > 0) {
|
|
479
|
-
console.log('Video stopped with error, using captured frames');
|
|
480
458
|
const result: VideoRecordingResult = {
|
|
481
459
|
frames,
|
|
482
460
|
duration: actualDuration,
|
|
@@ -487,8 +465,6 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
487
465
|
};
|
|
488
466
|
onComplete(result);
|
|
489
467
|
} else {
|
|
490
|
-
// Even if we don't have enough frames, try to complete with what we have
|
|
491
|
-
console.log('Completing with available data despite error');
|
|
492
468
|
const result: VideoRecordingResult = {
|
|
493
469
|
frames: frames.length > 0 ? frames : [],
|
|
494
470
|
duration: actualDuration,
|
|
@@ -501,14 +477,22 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
501
477
|
}
|
|
502
478
|
}
|
|
503
479
|
} else {
|
|
504
|
-
console.log('No video recording ref - completing with captured frames');
|
|
505
480
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
506
481
|
|
|
507
|
-
|
|
482
|
+
if (frames.length === 0 && actualDuration < minDurationMs) {
|
|
483
|
+
logger.error('No frames and duration too short, cannot complete');
|
|
484
|
+
setPhase('recording');
|
|
485
|
+
Alert.alert(
|
|
486
|
+
strings.errors.videoTooShort?.title || 'Recording Too Short',
|
|
487
|
+
strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`,
|
|
488
|
+
[{ text: strings.common.retry || 'OK', onPress: resetAndRetry }]
|
|
489
|
+
);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
508
493
|
setPhase('processing');
|
|
509
494
|
setOverallProgress(100);
|
|
510
495
|
|
|
511
|
-
// Complete with frames we have
|
|
512
496
|
const result: VideoRecordingResult = {
|
|
513
497
|
frames: frames.length > 0 ? frames : [],
|
|
514
498
|
duration: actualDuration,
|
|
@@ -518,40 +502,27 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
518
502
|
sessionId,
|
|
519
503
|
};
|
|
520
504
|
|
|
521
|
-
console.log('Completing without video ref:', {
|
|
522
|
-
frames: result.frames.length,
|
|
523
|
-
duration: (actualDuration / 1000).toFixed(1) + 's',
|
|
524
|
-
challenges: completedChallenges.length
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
// Small delay to ensure UI updates
|
|
528
505
|
setTimeout(() => {
|
|
529
506
|
onComplete(result);
|
|
530
507
|
}, 100);
|
|
531
508
|
}
|
|
532
|
-
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs]);
|
|
509
|
+
}, [frames, completedChallenges, challenges, sessionId, onComplete, minDurationMs, resetAndRetry, strings]);
|
|
533
510
|
|
|
534
511
|
const runChallenge = useCallback((index: number) => {
|
|
535
512
|
if (index >= challenges.length) {
|
|
536
|
-
console.log('All challenges completed, checking duration');
|
|
537
513
|
setOverallProgress(100);
|
|
538
514
|
|
|
539
|
-
// Clear the totalDuration timeout since we're handling it manually
|
|
540
515
|
if (recordingTimeoutRef.current) {
|
|
541
516
|
clearTimeout(recordingTimeoutRef.current);
|
|
542
517
|
recordingTimeoutRef.current = null;
|
|
543
|
-
console.log('Cleared totalDuration timeout');
|
|
544
518
|
}
|
|
545
519
|
|
|
546
520
|
if (isRecordingRef.current) {
|
|
547
521
|
const elapsed = Date.now() - recordingStartTime.current;
|
|
548
|
-
console.log('Checking duration - Elapsed:', (elapsed / 1000).toFixed(1), 's, Min required:', (minDurationMs / 1000).toFixed(1), 's');
|
|
549
522
|
|
|
550
523
|
if (elapsed < minDurationMs) {
|
|
551
524
|
const remaining = minDurationMs - elapsed;
|
|
552
|
-
console.log('Waiting additional', (remaining / 1000).toFixed(1), 's to meet minimum duration');
|
|
553
525
|
recordingTimeoutRef.current = setTimeout(() => {
|
|
554
|
-
console.log('Minimum duration wait completed, stopping recording');
|
|
555
526
|
if (isRecordingRef.current) {
|
|
556
527
|
stopRecording();
|
|
557
528
|
}
|
|
@@ -559,13 +530,10 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
559
530
|
return;
|
|
560
531
|
}
|
|
561
532
|
|
|
562
|
-
console.log('Duration requirement met, stopping immediately');
|
|
563
533
|
stopRecording();
|
|
564
534
|
} else {
|
|
565
|
-
console.log('Recording already stopped, checking if we can complete');
|
|
566
535
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
567
536
|
if (actualDuration >= minDurationMs && frames.length > 0) {
|
|
568
|
-
console.log('Recording already stopped, completing with frames');
|
|
569
537
|
const result: VideoRecordingResult = {
|
|
570
538
|
frames,
|
|
571
539
|
duration: actualDuration,
|
|
@@ -632,47 +600,35 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
632
600
|
recordingStartTime.current = Date.now();
|
|
633
601
|
isRecordingRef.current = true;
|
|
634
602
|
|
|
635
|
-
console.log('Starting video recording, total duration:', (totalDuration / 1000).toFixed(1), 's');
|
|
636
|
-
|
|
637
603
|
if (cameraRef.current && device) {
|
|
638
604
|
try {
|
|
639
605
|
videoRecordingRef.current = await cameraRef.current.startRecording({
|
|
640
606
|
flash: 'off',
|
|
641
607
|
onRecordingFinished: (video: any) => {
|
|
642
|
-
|
|
643
|
-
// Ensure we're in the right state to process
|
|
644
|
-
if (phase === 'recording' || phase === 'processing') {
|
|
645
|
-
handleVideoComplete(video);
|
|
646
|
-
} else {
|
|
647
|
-
console.warn('Received onRecordingFinished but phase is', phase, '- calling handleVideoComplete anyway');
|
|
648
|
-
handleVideoComplete(video);
|
|
649
|
-
}
|
|
608
|
+
handleVideoComplete(video);
|
|
650
609
|
},
|
|
651
610
|
onRecordingError: (error: any) => {
|
|
652
|
-
|
|
611
|
+
logger.error('Recording error:', error);
|
|
653
612
|
handleRecordingError(error);
|
|
654
613
|
},
|
|
655
614
|
});
|
|
656
|
-
|
|
615
|
+
|
|
616
|
+
startFrameCapture();
|
|
657
617
|
} catch (error) {
|
|
658
|
-
|
|
618
|
+
logger.warn('Video recording not available, falling back to frame capture');
|
|
659
619
|
startFrameCapture();
|
|
660
620
|
}
|
|
661
621
|
} else {
|
|
662
|
-
console.log('Camera not available, using frame capture');
|
|
663
622
|
startFrameCapture();
|
|
664
623
|
}
|
|
665
624
|
|
|
666
625
|
runChallenge(0);
|
|
667
626
|
|
|
668
|
-
|
|
669
|
-
// This will be cleared if challenges complete early
|
|
670
|
-
const maxDuration = Math.max(totalDuration, minDurationMs + 2000); // Add 2s buffer
|
|
627
|
+
const maxDuration = Math.max(totalDuration, minDurationMs + 2000);
|
|
671
628
|
recordingTimeoutRef.current = setTimeout(() => {
|
|
672
|
-
console.log('Safety timeout reached (max duration), stopping recording');
|
|
673
629
|
if (isRecordingRef.current) {
|
|
674
630
|
stopRecording().catch(err => {
|
|
675
|
-
|
|
631
|
+
logger.error('Error stopping recording on safety timeout:', err);
|
|
676
632
|
});
|
|
677
633
|
}
|
|
678
634
|
}, maxDuration);
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
BiometricIdentitySDK,
|
|
4
4
|
SDKState,
|
|
5
5
|
VideoResult,
|
|
6
|
+
logger,
|
|
6
7
|
} from '@hexar/biometric-identity-sdk-core';
|
|
7
8
|
|
|
8
9
|
export interface ChallengeAction {
|
|
@@ -46,7 +47,7 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
46
47
|
setState(sdk.getState());
|
|
47
48
|
}
|
|
48
49
|
} catch (error) {
|
|
49
|
-
|
|
50
|
+
logger.error('SDK initialization failed:', error);
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
|
|
@@ -101,7 +102,7 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
101
102
|
setChallenges(defaultChallenges);
|
|
102
103
|
return defaultChallenges;
|
|
103
104
|
} catch (error) {
|
|
104
|
-
|
|
105
|
+
logger.error('Failed to fetch challenges:', error);
|
|
105
106
|
const defaultChallenges: ChallengeAction[] = sdk.getDefaultChallenges().map(c => ({
|
|
106
107
|
action: c.action,
|
|
107
108
|
instruction: c.instruction,
|
|
@@ -192,17 +193,11 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
192
193
|
* Validate identity with all collected data
|
|
193
194
|
*/
|
|
194
195
|
const validateIdentity = useCallback(async () => {
|
|
195
|
-
console.log('validateIdentity called, current state:', sdk.getState().currentStep);
|
|
196
|
-
|
|
197
|
-
// Update state immediately to show validation screen
|
|
198
196
|
setState(sdk.getState());
|
|
199
197
|
|
|
200
|
-
// Poll state during validation to catch intermediate updates
|
|
201
198
|
const pollInterval = setInterval(() => {
|
|
202
199
|
if (isMounted.current) {
|
|
203
|
-
|
|
204
|
-
setState(currentState);
|
|
205
|
-
console.log('State polled - step:', currentState.currentStep, 'progress:', currentState.progress);
|
|
200
|
+
setState(sdk.getState());
|
|
206
201
|
}
|
|
207
202
|
}, 200);
|
|
208
203
|
|
|
@@ -211,9 +206,7 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
211
206
|
clearInterval(pollInterval);
|
|
212
207
|
|
|
213
208
|
if (isMounted.current) {
|
|
214
|
-
|
|
215
|
-
setState(finalState);
|
|
216
|
-
console.log('Validation complete, final state:', finalState.currentStep, 'result:', result);
|
|
209
|
+
setState(sdk.getState());
|
|
217
210
|
}
|
|
218
211
|
return result;
|
|
219
212
|
} catch (error) {
|