@hexar/biometric-identity-sdk-react-native 1.0.19 → 1.0.21
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 +24 -18
- package/dist/components/VideoRecorder.d.ts.map +1 -1
- package/dist/components/VideoRecorder.js +42 -18
- package/dist/hooks/useBiometricSDK.d.ts.map +1 -1
- package/dist/hooks/useBiometricSDK.js +19 -20
- package/package.json +2 -2
- package/src/components/BiometricIdentityFlow.tsx +28 -18
- package/src/components/VideoRecorder.tsx +44 -18
- package/src/hooks/useBiometricSDK.ts +19 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,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,
|
|
1
|
+
{"version":3,"file":"BiometricIdentityFlow.d.ts","sourceRoot":"","sources":["../../src/components/BiometricIdentityFlow.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AACxE,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,CA2WtE,CAAC;AA8NF,eAAe,qBAAqB,CAAC"}
|
|
@@ -70,9 +70,7 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
70
70
|
const strings = (0, biometric_identity_sdk_core_1.getStrings)();
|
|
71
71
|
const styles = createStyles(theme);
|
|
72
72
|
(0, react_1.useEffect)(() => {
|
|
73
|
-
console.log('🔵 [BiometricIdentityFlow] useEffect triggered - currentStep:', state.currentStep, 'hasResult:', !!state.validationResult, 'hasCalled:', hasCalledValidationComplete.current);
|
|
74
73
|
if (state.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
75
|
-
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete callback');
|
|
76
74
|
biometric_identity_sdk_core_1.logger.info('Validation completed, calling onValidationComplete callback');
|
|
77
75
|
hasCalledValidationComplete.current = true;
|
|
78
76
|
onValidationCompleteRef.current(state.validationResult);
|
|
@@ -125,6 +123,14 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
125
123
|
}
|
|
126
124
|
else if (cameraMode === 'video') {
|
|
127
125
|
const videoResult = data;
|
|
126
|
+
biometric_identity_sdk_core_1.logger.info('Video recording received', {
|
|
127
|
+
framesCount: videoResult.frames?.length || 0,
|
|
128
|
+
duration: videoResult.duration
|
|
129
|
+
});
|
|
130
|
+
if (!videoResult.frames || videoResult.frames.length === 0) {
|
|
131
|
+
biometric_identity_sdk_core_1.logger.error('Video recording has no frames');
|
|
132
|
+
throw new Error('Video recording contains no frames');
|
|
133
|
+
}
|
|
128
134
|
await storeVideoRecording({
|
|
129
135
|
frames: videoResult.frames,
|
|
130
136
|
duration: videoResult.duration,
|
|
@@ -133,43 +139,43 @@ const BiometricIdentityFlow = ({ onValidationComplete, onError, theme, language,
|
|
|
133
139
|
challengesCompleted: videoResult.challengesCompleted,
|
|
134
140
|
sessionId: videoResult.sessionId,
|
|
135
141
|
});
|
|
136
|
-
|
|
137
|
-
biometric_identity_sdk_core_1.logger.info('
|
|
142
|
+
const stateAfterStore = sdk.getState();
|
|
143
|
+
biometric_identity_sdk_core_1.logger.info('Video stored, verifying state before validation', {
|
|
144
|
+
storedFramesCount: stateAfterStore.videoData?.frames?.length || 0,
|
|
145
|
+
hasVideoData: !!stateAfterStore.videoData
|
|
146
|
+
});
|
|
147
|
+
if (!stateAfterStore.videoData || !stateAfterStore.videoData.frames || stateAfterStore.videoData.frames.length === 0) {
|
|
148
|
+
biometric_identity_sdk_core_1.logger.error('Video data not properly stored in SDK state');
|
|
149
|
+
throw new Error('Video data was not properly stored');
|
|
150
|
+
}
|
|
151
|
+
biometric_identity_sdk_core_1.logger.info('Starting validation');
|
|
138
152
|
let result = null;
|
|
139
153
|
try {
|
|
140
154
|
result = await validateIdentity();
|
|
141
|
-
|
|
142
|
-
biometric_identity_sdk_core_1.logger.info('Validation completed successfully:', result);
|
|
155
|
+
biometric_identity_sdk_core_1.logger.info('Validation completed successfully');
|
|
143
156
|
}
|
|
144
157
|
catch (validationError) {
|
|
145
|
-
console.error('❌ [BiometricIdentityFlow] Validation error:', validationError);
|
|
146
158
|
biometric_identity_sdk_core_1.logger.error('Validation error:', validationError);
|
|
147
|
-
throw validationError;
|
|
159
|
+
throw validationError;
|
|
148
160
|
}
|
|
149
161
|
const finalState = sdk.getState();
|
|
150
|
-
|
|
162
|
+
biometric_identity_sdk_core_1.logger.info('Final state after validation', {
|
|
151
163
|
currentStep: finalState.currentStep,
|
|
152
|
-
hasResult: !!finalState.validationResult
|
|
153
|
-
|
|
154
|
-
isLoading: finalState.isLoading
|
|
155
|
-
}));
|
|
156
|
-
biometric_identity_sdk_core_1.logger.info('Final state after validation:', finalState);
|
|
164
|
+
hasResult: !!finalState.validationResult
|
|
165
|
+
});
|
|
157
166
|
if (result && !hasCalledValidationComplete.current) {
|
|
158
|
-
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete directly with result');
|
|
159
167
|
biometric_identity_sdk_core_1.logger.info('Calling onValidationComplete directly with validation result');
|
|
160
168
|
hasCalledValidationComplete.current = true;
|
|
161
169
|
onValidationCompleteRef.current(result);
|
|
162
170
|
}
|
|
163
171
|
if (finalState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
164
|
-
|
|
165
|
-
biometric_identity_sdk_core_1.logger.info('Backup: Calling onValidationComplete from state');
|
|
172
|
+
biometric_identity_sdk_core_1.logger.info('Calling onValidationComplete from state');
|
|
166
173
|
hasCalledValidationComplete.current = true;
|
|
167
174
|
onValidationCompleteRef.current(finalState.validationResult);
|
|
168
175
|
}
|
|
169
176
|
}
|
|
170
177
|
}
|
|
171
178
|
catch (error) {
|
|
172
|
-
console.error('❌ [BiometricIdentityFlow] Capture/validation error:', error);
|
|
173
179
|
biometric_identity_sdk_core_1.logger.error('Capture/validation error:', error);
|
|
174
180
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
175
181
|
onErrorRef.current(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,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,
|
|
1
|
+
{"version":3,"file":"VideoRecorder.d.ts","sourceRoot":"","sources":["../../src/components/VideoRecorder.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAaxE,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,iBAAiB,EAAmC,MAAM,oCAAoC,CAAC;AAE1I,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,YAAY,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACrC,0CAA0C;IAC1C,UAAU,CAAC,EAAE,eAAe,EAAE,CAAC;IAC/B,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,EAAE,CAAC,SAAS,EAAE,oBAAoB,KAAK,IAAI,CAAC;IACtD,iCAAiC;IACjC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;CACtD;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,OAAO,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA8CD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAqzBtD,CAAC;AA4OF,eAAe,aAAa,CAAC"}
|
|
@@ -100,6 +100,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
100
100
|
const [overallProgress, setOverallProgress] = (0, react_1.useState)(0);
|
|
101
101
|
const [completedChallenges, setCompletedChallenges] = (0, react_1.useState)([]);
|
|
102
102
|
const [frames, setFrames] = (0, react_1.useState)([]);
|
|
103
|
+
const framesRef = (0, react_1.useRef)([]);
|
|
103
104
|
const [hasPermission, setHasPermission] = (0, react_1.useState)(false);
|
|
104
105
|
const cameraRef = (0, react_1.useRef)(null);
|
|
105
106
|
const { hasPermission: cameraPermission, requestPermission } = (0, react_native_vision_camera_1.useCameraPermission)();
|
|
@@ -257,6 +258,7 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
257
258
|
}, [arrowAnim]);
|
|
258
259
|
const resetAndRetry = (0, react_1.useCallback)(() => {
|
|
259
260
|
setFrames([]);
|
|
261
|
+
framesRef.current = [];
|
|
260
262
|
setCompletedChallenges([]);
|
|
261
263
|
setCurrentChallengeIndex(0);
|
|
262
264
|
setChallengeProgress(0);
|
|
@@ -293,16 +295,18 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
293
295
|
biometric_identity_sdk_core_1.logger.warn('Could not read video file, using captured frames');
|
|
294
296
|
}
|
|
295
297
|
}
|
|
296
|
-
|
|
298
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
299
|
+
let finalFrames = currentFrames.length > 0 ? currentFrames : [];
|
|
297
300
|
if (finalFrames.length === 0 && videoBase64) {
|
|
298
301
|
finalFrames = [videoBase64];
|
|
299
302
|
}
|
|
300
303
|
if (finalFrames.length === 0) {
|
|
301
|
-
biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete');
|
|
304
|
+
biometric_identity_sdk_core_1.logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
302
305
|
setPhase('recording');
|
|
303
306
|
handleRecordingError(new Error('No video frames captured'));
|
|
304
307
|
return;
|
|
305
308
|
}
|
|
309
|
+
biometric_identity_sdk_core_1.logger.info('Using frames for completion:', finalFrames.length, 'frames');
|
|
306
310
|
const result = {
|
|
307
311
|
frames: finalFrames,
|
|
308
312
|
duration: actualDuration,
|
|
@@ -324,7 +328,16 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
324
328
|
}, [frames, completedChallenges, challenges, sessionId, onComplete, resetAndRetry, handleRecordingError, strings, minDurationMs, phase]);
|
|
325
329
|
const startFrameCapture = (0, react_1.useCallback)(() => {
|
|
326
330
|
if (cameraRef.current && device) {
|
|
331
|
+
biometric_identity_sdk_core_1.logger.info('Starting frame capture');
|
|
332
|
+
framesRef.current = [];
|
|
327
333
|
frameCaptureInterval.current = setInterval(async () => {
|
|
334
|
+
if (!isRecordingRef.current) {
|
|
335
|
+
if (frameCaptureInterval.current) {
|
|
336
|
+
clearInterval(frameCaptureInterval.current);
|
|
337
|
+
frameCaptureInterval.current = null;
|
|
338
|
+
}
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
328
341
|
try {
|
|
329
342
|
const photo = await cameraRef.current?.takePhoto({
|
|
330
343
|
flash: 'off',
|
|
@@ -334,26 +347,32 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
334
347
|
const RNFS = require('react-native-fs');
|
|
335
348
|
const base64 = await RNFS.readFile(photo.path, 'base64');
|
|
336
349
|
setFrames(prev => {
|
|
337
|
-
|
|
338
|
-
|
|
350
|
+
const newFrames = prev.length < 100 ? [...prev, base64] : prev;
|
|
351
|
+
framesRef.current = newFrames;
|
|
352
|
+
if (newFrames.length % 10 === 0) {
|
|
353
|
+
biometric_identity_sdk_core_1.logger.info('Captured frames:', newFrames.length);
|
|
339
354
|
}
|
|
340
|
-
return
|
|
355
|
+
return newFrames;
|
|
341
356
|
});
|
|
342
357
|
}
|
|
343
358
|
catch (fsError) {
|
|
359
|
+
biometric_identity_sdk_core_1.logger.warn('Could not read photo file, using path:', fsError);
|
|
344
360
|
setFrames(prev => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return prev;
|
|
361
|
+
const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
|
|
362
|
+
framesRef.current = newFrames;
|
|
363
|
+
return newFrames;
|
|
349
364
|
});
|
|
350
365
|
}
|
|
351
366
|
}
|
|
352
367
|
}
|
|
353
368
|
catch (error) {
|
|
369
|
+
biometric_identity_sdk_core_1.logger.warn('Frame capture error:', error);
|
|
354
370
|
}
|
|
355
371
|
}, 100);
|
|
356
372
|
}
|
|
373
|
+
else {
|
|
374
|
+
biometric_identity_sdk_core_1.logger.warn('Cannot start frame capture: camera or device not available');
|
|
375
|
+
}
|
|
357
376
|
}, [device]);
|
|
358
377
|
const stopRecording = (0, react_1.useCallback)(async () => {
|
|
359
378
|
if (recordingTimeoutRef.current) {
|
|
@@ -377,23 +396,26 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
377
396
|
catch (error) {
|
|
378
397
|
biometric_identity_sdk_core_1.logger.error('Error stopping video recording:', error);
|
|
379
398
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
380
|
-
|
|
399
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
400
|
+
if (actualDuration >= minDurationMs && currentFrames.length > 0) {
|
|
401
|
+
biometric_identity_sdk_core_1.logger.info('Stopping recording with frames:', currentFrames.length);
|
|
381
402
|
const result = {
|
|
382
|
-
frames,
|
|
403
|
+
frames: currentFrames,
|
|
383
404
|
duration: actualDuration,
|
|
384
405
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
385
|
-
qualityScore: Math.min(100, (
|
|
406
|
+
qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
|
|
386
407
|
challengesCompleted: completedChallenges,
|
|
387
408
|
sessionId,
|
|
388
409
|
};
|
|
389
410
|
onComplete(result);
|
|
390
411
|
}
|
|
391
412
|
else {
|
|
413
|
+
biometric_identity_sdk_core_1.logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
392
414
|
const result = {
|
|
393
|
-
frames:
|
|
415
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
394
416
|
duration: actualDuration,
|
|
395
417
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
396
|
-
qualityScore:
|
|
418
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
397
419
|
challengesCompleted: completedChallenges,
|
|
398
420
|
sessionId,
|
|
399
421
|
};
|
|
@@ -403,19 +425,21 @@ const VideoRecorder = ({ theme, language, duration, instructions, challenges: pr
|
|
|
403
425
|
}
|
|
404
426
|
else {
|
|
405
427
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
406
|
-
|
|
407
|
-
|
|
428
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
429
|
+
if (currentFrames.length === 0 && actualDuration < minDurationMs) {
|
|
430
|
+
biometric_identity_sdk_core_1.logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
408
431
|
setPhase('recording');
|
|
409
432
|
react_native_1.Alert.alert(strings.errors.videoTooShort?.title || 'Recording Too Short', strings.errors.videoTooShort?.message || `Video must be at least ${minDurationMs / 1000} seconds. Please try again.`, [{ text: strings.common.retry || 'OK', onPress: resetAndRetry }]);
|
|
410
433
|
return;
|
|
411
434
|
}
|
|
412
435
|
setPhase('processing');
|
|
413
436
|
setOverallProgress(100);
|
|
437
|
+
biometric_identity_sdk_core_1.logger.info('Completing recording with frames:', currentFrames.length);
|
|
414
438
|
const result = {
|
|
415
|
-
frames:
|
|
439
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
416
440
|
duration: actualDuration,
|
|
417
441
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
418
|
-
qualityScore:
|
|
442
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
419
443
|
challengesCompleted: completedChallenges,
|
|
420
444
|
sessionId,
|
|
421
445
|
};
|
|
@@ -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,EAGZ,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,EAGZ,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,qBA2QlC,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -137,9 +137,13 @@ const useBiometricSDK = () => {
|
|
|
137
137
|
* Validate identity with all collected data
|
|
138
138
|
*/
|
|
139
139
|
const validateIdentity = (0, react_1.useCallback)(async () => {
|
|
140
|
-
|
|
141
|
-
biometric_identity_sdk_core_1.logger.info('validateIdentity called
|
|
142
|
-
|
|
140
|
+
const currentState = sdk.getState();
|
|
141
|
+
biometric_identity_sdk_core_1.logger.info('validateIdentity called', {
|
|
142
|
+
hasFrontID: !!currentState.frontID,
|
|
143
|
+
hasVideoData: !!currentState.videoData,
|
|
144
|
+
videoFramesCount: currentState.videoData?.frames?.length || 0
|
|
145
|
+
});
|
|
146
|
+
setState(currentState);
|
|
143
147
|
const pollInterval = setInterval(() => {
|
|
144
148
|
if (isMounted.current) {
|
|
145
149
|
const currentState = sdk.getState();
|
|
@@ -148,44 +152,41 @@ const useBiometricSDK = () => {
|
|
|
148
152
|
}, 200);
|
|
149
153
|
let finalPollInterval = null;
|
|
150
154
|
try {
|
|
151
|
-
|
|
155
|
+
biometric_identity_sdk_core_1.logger.info('Calling sdk.validateIdentity');
|
|
152
156
|
const result = await sdk.validateIdentity();
|
|
153
|
-
|
|
154
|
-
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
155
|
-
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
157
|
+
biometric_identity_sdk_core_1.logger.info('sdk.validateIdentity completed');
|
|
156
158
|
let pollCount = 0;
|
|
157
|
-
const maxPolls = 10;
|
|
159
|
+
const maxPolls = 10;
|
|
158
160
|
finalPollInterval = setInterval(() => {
|
|
159
161
|
pollCount++;
|
|
160
162
|
if (isMounted.current) {
|
|
161
163
|
const currentState = sdk.getState();
|
|
162
|
-
biometric_identity_sdk_core_1.logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
163
164
|
setState(currentState);
|
|
164
|
-
// Stop polling once we reach RESULT or ERROR state
|
|
165
165
|
if (currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || currentState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
166
|
-
console.log('🟢 [useBiometricSDK] Reached final state:', currentState.currentStep);
|
|
167
166
|
if (finalPollInterval)
|
|
168
167
|
clearInterval(finalPollInterval);
|
|
169
168
|
clearInterval(pollInterval);
|
|
170
|
-
biometric_identity_sdk_core_1.logger.info('Reached final state, stopped polling
|
|
169
|
+
biometric_identity_sdk_core_1.logger.info('Reached final state, stopped polling', {
|
|
170
|
+
step: currentState.currentStep
|
|
171
|
+
});
|
|
171
172
|
}
|
|
172
173
|
else if (pollCount >= maxPolls) {
|
|
173
|
-
// Force stop after max polls
|
|
174
174
|
if (finalPollInterval)
|
|
175
175
|
clearInterval(finalPollInterval);
|
|
176
176
|
clearInterval(pollInterval);
|
|
177
177
|
const finalState = sdk.getState();
|
|
178
|
-
biometric_identity_sdk_core_1.logger.warn('Max polls reached, forcing final state
|
|
178
|
+
biometric_identity_sdk_core_1.logger.warn('Max polls reached, forcing final state');
|
|
179
179
|
setState(finalState);
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
}, 200);
|
|
183
|
-
// Also get immediate state after promise resolves
|
|
184
183
|
if (isMounted.current) {
|
|
185
184
|
const immediateState = sdk.getState();
|
|
186
|
-
biometric_identity_sdk_core_1.logger.info('Validation result received, immediate state
|
|
185
|
+
biometric_identity_sdk_core_1.logger.info('Validation result received, immediate state', {
|
|
186
|
+
step: immediateState.currentStep,
|
|
187
|
+
hasResult: !!immediateState.validationResult
|
|
188
|
+
});
|
|
187
189
|
setState(immediateState);
|
|
188
|
-
// If we're already at RESULT, clear intervals immediately
|
|
189
190
|
if (immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.RESULT || immediateState.currentStep === biometric_identity_sdk_core_1.SDKStep.ERROR) {
|
|
190
191
|
if (finalPollInterval)
|
|
191
192
|
clearInterval(finalPollInterval);
|
|
@@ -195,13 +196,11 @@ const useBiometricSDK = () => {
|
|
|
195
196
|
return result;
|
|
196
197
|
}
|
|
197
198
|
catch (error) {
|
|
198
|
-
// Clear all intervals on error
|
|
199
199
|
clearInterval(pollInterval);
|
|
200
200
|
if (finalPollInterval) {
|
|
201
201
|
clearInterval(finalPollInterval);
|
|
202
202
|
}
|
|
203
|
-
|
|
204
|
-
biometric_identity_sdk_core_1.logger.error('Validation error, state:', sdk.getState(), error);
|
|
203
|
+
biometric_identity_sdk_core_1.logger.error('Validation error', error);
|
|
205
204
|
if (isMounted.current) {
|
|
206
205
|
const errorState = sdk.getState();
|
|
207
206
|
setState(errorState);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexar/biometric-identity-sdk-react-native",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "React Native wrapper for Biometric Identity SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"clean": "rm -rf dist"
|
|
12
12
|
},
|
|
13
13
|
"peerDependencies": {
|
|
14
|
-
"@hexar/biometric-identity-sdk-core": ">=1.0.
|
|
14
|
+
"@hexar/biometric-identity-sdk-core": ">=1.0.12",
|
|
15
15
|
"react": ">=18.0.0",
|
|
16
16
|
"react-native": ">=0.70.0",
|
|
17
17
|
"react-native-permissions": ">=4.0.0",
|
|
@@ -96,9 +96,7 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
96
96
|
const styles = createStyles(theme);
|
|
97
97
|
|
|
98
98
|
useEffect(() => {
|
|
99
|
-
console.log('🔵 [BiometricIdentityFlow] useEffect triggered - currentStep:', state.currentStep, 'hasResult:', !!state.validationResult, 'hasCalled:', hasCalledValidationComplete.current);
|
|
100
99
|
if (state.currentStep === SDKStep.RESULT && state.validationResult && !hasCalledValidationComplete.current) {
|
|
101
|
-
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete callback');
|
|
102
100
|
logger.info('Validation completed, calling onValidationComplete callback');
|
|
103
101
|
hasCalledValidationComplete.current = true;
|
|
104
102
|
onValidationCompleteRef.current(state.validationResult);
|
|
@@ -155,6 +153,16 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
155
153
|
} else if (cameraMode === 'video') {
|
|
156
154
|
const videoResult: VideoRecordingResult = data;
|
|
157
155
|
|
|
156
|
+
logger.info('Video recording received', {
|
|
157
|
+
framesCount: videoResult.frames?.length || 0,
|
|
158
|
+
duration: videoResult.duration
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
if (!videoResult.frames || videoResult.frames.length === 0) {
|
|
162
|
+
logger.error('Video recording has no frames');
|
|
163
|
+
throw new Error('Video recording contains no frames');
|
|
164
|
+
}
|
|
165
|
+
|
|
158
166
|
await storeVideoRecording({
|
|
159
167
|
frames: videoResult.frames,
|
|
160
168
|
duration: videoResult.duration,
|
|
@@ -164,45 +172,47 @@ export const BiometricIdentityFlow: React.FC<BiometricIdentityFlowProps> = ({
|
|
|
164
172
|
sessionId: videoResult.sessionId,
|
|
165
173
|
});
|
|
166
174
|
|
|
167
|
-
|
|
168
|
-
logger.info('
|
|
175
|
+
const stateAfterStore = sdk.getState();
|
|
176
|
+
logger.info('Video stored, verifying state before validation', {
|
|
177
|
+
storedFramesCount: stateAfterStore.videoData?.frames?.length || 0,
|
|
178
|
+
hasVideoData: !!stateAfterStore.videoData
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (!stateAfterStore.videoData || !stateAfterStore.videoData.frames || stateAfterStore.videoData.frames.length === 0) {
|
|
182
|
+
logger.error('Video data not properly stored in SDK state');
|
|
183
|
+
throw new Error('Video data was not properly stored');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
logger.info('Starting validation');
|
|
169
187
|
|
|
170
188
|
let result: ValidationResult | null = null;
|
|
171
189
|
try {
|
|
172
190
|
result = await validateIdentity();
|
|
173
|
-
|
|
174
|
-
logger.info('Validation completed successfully:', result);
|
|
191
|
+
logger.info('Validation completed successfully');
|
|
175
192
|
} catch (validationError) {
|
|
176
|
-
console.error('❌ [BiometricIdentityFlow] Validation error:', validationError);
|
|
177
193
|
logger.error('Validation error:', validationError);
|
|
178
|
-
throw validationError;
|
|
194
|
+
throw validationError;
|
|
179
195
|
}
|
|
180
196
|
|
|
181
197
|
const finalState = sdk.getState();
|
|
182
|
-
|
|
198
|
+
logger.info('Final state after validation', {
|
|
183
199
|
currentStep: finalState.currentStep,
|
|
184
|
-
hasResult: !!finalState.validationResult
|
|
185
|
-
|
|
186
|
-
isLoading: finalState.isLoading
|
|
187
|
-
}));
|
|
188
|
-
logger.info('Final state after validation:', finalState);
|
|
200
|
+
hasResult: !!finalState.validationResult
|
|
201
|
+
});
|
|
189
202
|
|
|
190
203
|
if (result && !hasCalledValidationComplete.current) {
|
|
191
|
-
console.log('🟢 [BiometricIdentityFlow] Calling onValidationComplete directly with result');
|
|
192
204
|
logger.info('Calling onValidationComplete directly with validation result');
|
|
193
205
|
hasCalledValidationComplete.current = true;
|
|
194
206
|
onValidationCompleteRef.current(result);
|
|
195
207
|
}
|
|
196
208
|
|
|
197
209
|
if (finalState.currentStep === SDKStep.RESULT && finalState.validationResult && !hasCalledValidationComplete.current) {
|
|
198
|
-
|
|
199
|
-
logger.info('Backup: Calling onValidationComplete from state');
|
|
210
|
+
logger.info('Calling onValidationComplete from state');
|
|
200
211
|
hasCalledValidationComplete.current = true;
|
|
201
212
|
onValidationCompleteRef.current(finalState.validationResult);
|
|
202
213
|
}
|
|
203
214
|
}
|
|
204
215
|
} catch (error) {
|
|
205
|
-
console.error('❌ [BiometricIdentityFlow] Capture/validation error:', error);
|
|
206
216
|
logger.error('Capture/validation error:', error);
|
|
207
217
|
|
|
208
218
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
@@ -127,6 +127,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
127
127
|
const [overallProgress, setOverallProgress] = useState(0);
|
|
128
128
|
const [completedChallenges, setCompletedChallenges] = useState<string[]>([]);
|
|
129
129
|
const [frames, setFrames] = useState<string[]>([]);
|
|
130
|
+
const framesRef = useRef<string[]>([]);
|
|
130
131
|
const [hasPermission, setHasPermission] = useState(false);
|
|
131
132
|
|
|
132
133
|
const cameraRef = useRef<Camera>(null);
|
|
@@ -304,6 +305,7 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
304
305
|
|
|
305
306
|
const resetAndRetry = useCallback(() => {
|
|
306
307
|
setFrames([]);
|
|
308
|
+
framesRef.current = [];
|
|
307
309
|
setCompletedChallenges([]);
|
|
308
310
|
setCurrentChallengeIndex(0);
|
|
309
311
|
setChallengeProgress(0);
|
|
@@ -354,19 +356,22 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
354
356
|
}
|
|
355
357
|
}
|
|
356
358
|
|
|
357
|
-
|
|
359
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
360
|
+
let finalFrames = currentFrames.length > 0 ? currentFrames : [];
|
|
358
361
|
|
|
359
362
|
if (finalFrames.length === 0 && videoBase64) {
|
|
360
363
|
finalFrames = [videoBase64];
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
if (finalFrames.length === 0) {
|
|
364
|
-
logger.error('No frames or video available, cannot complete');
|
|
367
|
+
logger.error('No frames or video available, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
365
368
|
setPhase('recording');
|
|
366
369
|
handleRecordingError(new Error('No video frames captured'));
|
|
367
370
|
return;
|
|
368
371
|
}
|
|
369
372
|
|
|
373
|
+
logger.info('Using frames for completion:', finalFrames.length, 'frames');
|
|
374
|
+
|
|
370
375
|
const result: VideoRecordingResult = {
|
|
371
376
|
frames: finalFrames,
|
|
372
377
|
duration: actualDuration,
|
|
@@ -390,7 +395,17 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
390
395
|
|
|
391
396
|
const startFrameCapture = useCallback(() => {
|
|
392
397
|
if (cameraRef.current && device) {
|
|
398
|
+
logger.info('Starting frame capture');
|
|
399
|
+
framesRef.current = [];
|
|
393
400
|
frameCaptureInterval.current = setInterval(async () => {
|
|
401
|
+
if (!isRecordingRef.current) {
|
|
402
|
+
if (frameCaptureInterval.current) {
|
|
403
|
+
clearInterval(frameCaptureInterval.current);
|
|
404
|
+
frameCaptureInterval.current = null;
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
394
409
|
try {
|
|
395
410
|
const photo = await cameraRef.current?.takePhoto({
|
|
396
411
|
flash: 'off',
|
|
@@ -401,23 +416,28 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
401
416
|
const RNFS = require('react-native-fs');
|
|
402
417
|
const base64 = await RNFS.readFile(photo.path, 'base64');
|
|
403
418
|
setFrames(prev => {
|
|
404
|
-
|
|
405
|
-
|
|
419
|
+
const newFrames = prev.length < 100 ? [...prev, base64] : prev;
|
|
420
|
+
framesRef.current = newFrames;
|
|
421
|
+
if (newFrames.length % 10 === 0) {
|
|
422
|
+
logger.info('Captured frames:', newFrames.length);
|
|
406
423
|
}
|
|
407
|
-
return
|
|
424
|
+
return newFrames;
|
|
408
425
|
});
|
|
409
426
|
} catch (fsError) {
|
|
427
|
+
logger.warn('Could not read photo file, using path:', fsError);
|
|
410
428
|
setFrames(prev => {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
return prev;
|
|
429
|
+
const newFrames = prev.length < 100 ? [...prev, photo.path] : prev;
|
|
430
|
+
framesRef.current = newFrames;
|
|
431
|
+
return newFrames;
|
|
415
432
|
});
|
|
416
433
|
}
|
|
417
434
|
}
|
|
418
435
|
} catch (error) {
|
|
436
|
+
logger.warn('Frame capture error:', error);
|
|
419
437
|
}
|
|
420
438
|
}, 100);
|
|
439
|
+
} else {
|
|
440
|
+
logger.warn('Cannot start frame capture: camera or device not available');
|
|
421
441
|
}
|
|
422
442
|
}, [device]);
|
|
423
443
|
|
|
@@ -447,22 +467,26 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
447
467
|
logger.error('Error stopping video recording:', error);
|
|
448
468
|
|
|
449
469
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
450
|
-
|
|
470
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
471
|
+
|
|
472
|
+
if (actualDuration >= minDurationMs && currentFrames.length > 0) {
|
|
473
|
+
logger.info('Stopping recording with frames:', currentFrames.length);
|
|
451
474
|
const result: VideoRecordingResult = {
|
|
452
|
-
frames,
|
|
475
|
+
frames: currentFrames,
|
|
453
476
|
duration: actualDuration,
|
|
454
477
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
455
|
-
qualityScore: Math.min(100, (
|
|
478
|
+
qualityScore: Math.min(100, (currentFrames.length / 30) * 100),
|
|
456
479
|
challengesCompleted: completedChallenges,
|
|
457
480
|
sessionId,
|
|
458
481
|
};
|
|
459
482
|
onComplete(result);
|
|
460
483
|
} else {
|
|
484
|
+
logger.info('Stopping recording with frames (fallback):', currentFrames.length);
|
|
461
485
|
const result: VideoRecordingResult = {
|
|
462
|
-
frames:
|
|
486
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
463
487
|
duration: actualDuration,
|
|
464
488
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
465
|
-
qualityScore:
|
|
489
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
466
490
|
challengesCompleted: completedChallenges,
|
|
467
491
|
sessionId,
|
|
468
492
|
};
|
|
@@ -471,9 +495,10 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
471
495
|
}
|
|
472
496
|
} else {
|
|
473
497
|
const actualDuration = Date.now() - recordingStartTime.current;
|
|
498
|
+
const currentFrames = framesRef.current.length > 0 ? framesRef.current : frames;
|
|
474
499
|
|
|
475
|
-
if (
|
|
476
|
-
logger.error('No frames and duration too short, cannot complete');
|
|
500
|
+
if (currentFrames.length === 0 && actualDuration < minDurationMs) {
|
|
501
|
+
logger.error('No frames and duration too short, cannot complete. Frames ref:', framesRef.current.length, 'Frames state:', frames.length);
|
|
477
502
|
setPhase('recording');
|
|
478
503
|
Alert.alert(
|
|
479
504
|
strings.errors.videoTooShort?.title || 'Recording Too Short',
|
|
@@ -486,11 +511,12 @@ export const VideoRecorder: React.FC<VideoRecorderProps> = ({
|
|
|
486
511
|
setPhase('processing');
|
|
487
512
|
setOverallProgress(100);
|
|
488
513
|
|
|
514
|
+
logger.info('Completing recording with frames:', currentFrames.length);
|
|
489
515
|
const result: VideoRecordingResult = {
|
|
490
|
-
frames:
|
|
516
|
+
frames: currentFrames.length > 0 ? currentFrames : [],
|
|
491
517
|
duration: actualDuration,
|
|
492
518
|
instructionsFollowed: completedChallenges.length === challenges.length,
|
|
493
|
-
qualityScore:
|
|
519
|
+
qualityScore: currentFrames.length > 0 ? Math.min(100, (currentFrames.length / 30) * 100) : 0,
|
|
494
520
|
challengesCompleted: completedChallenges,
|
|
495
521
|
sessionId,
|
|
496
522
|
};
|
|
@@ -194,9 +194,13 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
194
194
|
* Validate identity with all collected data
|
|
195
195
|
*/
|
|
196
196
|
const validateIdentity = useCallback(async () => {
|
|
197
|
-
|
|
198
|
-
logger.info('validateIdentity called
|
|
199
|
-
|
|
197
|
+
const currentState = sdk.getState();
|
|
198
|
+
logger.info('validateIdentity called', {
|
|
199
|
+
hasFrontID: !!currentState.frontID,
|
|
200
|
+
hasVideoData: !!currentState.videoData,
|
|
201
|
+
videoFramesCount: currentState.videoData?.frames?.length || 0
|
|
202
|
+
});
|
|
203
|
+
setState(currentState);
|
|
200
204
|
|
|
201
205
|
const pollInterval = setInterval(() => {
|
|
202
206
|
if (isMounted.current) {
|
|
@@ -208,46 +212,43 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
208
212
|
let finalPollInterval: NodeJS.Timeout | null = null;
|
|
209
213
|
|
|
210
214
|
try {
|
|
211
|
-
|
|
215
|
+
logger.info('Calling sdk.validateIdentity');
|
|
212
216
|
const result = await sdk.validateIdentity();
|
|
213
|
-
|
|
217
|
+
logger.info('sdk.validateIdentity completed');
|
|
214
218
|
|
|
215
|
-
// Keep polling for a bit to ensure we capture the RESULT state transition
|
|
216
|
-
// The SDK updates state synchronously, but we want to make sure React sees it
|
|
217
219
|
let pollCount = 0;
|
|
218
|
-
const maxPolls = 10;
|
|
220
|
+
const maxPolls = 10;
|
|
219
221
|
|
|
220
222
|
finalPollInterval = setInterval(() => {
|
|
221
223
|
pollCount++;
|
|
222
224
|
if (isMounted.current) {
|
|
223
225
|
const currentState = sdk.getState();
|
|
224
|
-
logger.info(`Polling state (${pollCount}/${maxPolls}):`, currentState);
|
|
225
226
|
setState(currentState);
|
|
226
227
|
|
|
227
|
-
// Stop polling once we reach RESULT or ERROR state
|
|
228
228
|
if (currentState.currentStep === SDKStep.RESULT || currentState.currentStep === SDKStep.ERROR) {
|
|
229
|
-
console.log('🟢 [useBiometricSDK] Reached final state:', currentState.currentStep);
|
|
230
229
|
if (finalPollInterval) clearInterval(finalPollInterval);
|
|
231
230
|
clearInterval(pollInterval);
|
|
232
|
-
logger.info('Reached final state, stopped polling
|
|
231
|
+
logger.info('Reached final state, stopped polling', {
|
|
232
|
+
step: currentState.currentStep
|
|
233
|
+
});
|
|
233
234
|
} else if (pollCount >= maxPolls) {
|
|
234
|
-
// Force stop after max polls
|
|
235
235
|
if (finalPollInterval) clearInterval(finalPollInterval);
|
|
236
236
|
clearInterval(pollInterval);
|
|
237
237
|
const finalState = sdk.getState();
|
|
238
|
-
logger.warn('Max polls reached, forcing final state
|
|
238
|
+
logger.warn('Max polls reached, forcing final state');
|
|
239
239
|
setState(finalState);
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
}, 200);
|
|
243
243
|
|
|
244
|
-
// Also get immediate state after promise resolves
|
|
245
244
|
if (isMounted.current) {
|
|
246
245
|
const immediateState = sdk.getState();
|
|
247
|
-
logger.info('Validation result received, immediate state
|
|
246
|
+
logger.info('Validation result received, immediate state', {
|
|
247
|
+
step: immediateState.currentStep,
|
|
248
|
+
hasResult: !!immediateState.validationResult
|
|
249
|
+
});
|
|
248
250
|
setState(immediateState);
|
|
249
251
|
|
|
250
|
-
// If we're already at RESULT, clear intervals immediately
|
|
251
252
|
if (immediateState.currentStep === SDKStep.RESULT || immediateState.currentStep === SDKStep.ERROR) {
|
|
252
253
|
if (finalPollInterval) clearInterval(finalPollInterval);
|
|
253
254
|
clearInterval(pollInterval);
|
|
@@ -256,13 +257,11 @@ export const useBiometricSDK = (): UseBiometricSDKResult => {
|
|
|
256
257
|
|
|
257
258
|
return result;
|
|
258
259
|
} catch (error) {
|
|
259
|
-
// Clear all intervals on error
|
|
260
260
|
clearInterval(pollInterval);
|
|
261
261
|
if (finalPollInterval) {
|
|
262
262
|
clearInterval(finalPollInterval);
|
|
263
263
|
}
|
|
264
|
-
|
|
265
|
-
logger.error('Validation error, state:', sdk.getState(), error);
|
|
264
|
+
logger.error('Validation error', error);
|
|
266
265
|
|
|
267
266
|
if (isMounted.current) {
|
|
268
267
|
const errorState = sdk.getState();
|