@trustchex/react-native-sdk 1.478.7 → 1.483.4
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/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +57 -3
- package/ios/Camera/TrustchexCameraView.swift +20 -15
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +0 -3
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +21 -0
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +1 -1
- package/lib/module/Screens/Static/ResultScreen.js +75 -16
- package/lib/module/Shared/Components/DiagnosticReportButton.js +64 -0
- package/lib/module/Shared/Components/EIDScanner.js +52 -19
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +132 -8
- package/lib/module/Shared/Components/NavigationManager.js +7 -9
- package/lib/module/Shared/Contexts/AppContext.js +3 -0
- package/lib/module/Shared/EIDReader/eidReader.js +167 -30
- package/lib/module/Shared/EIDReader/lds/icao/dg11File.js +77 -0
- package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
- package/lib/module/Shared/Libs/diagnosticReport.js +133 -0
- package/lib/module/Shared/Libs/diagnostics.js +171 -0
- package/lib/module/Shared/Libs/documentDataNormalizer.js +104 -0
- package/lib/module/Shared/Libs/mrz.utils.js +0 -5
- package/lib/module/Shared/Libs/mrzFrameAggregator.js +32 -5
- package/lib/module/Shared/Libs/mrzTransliteration.js +80 -0
- package/lib/module/Shared/Libs/native-device-info.utils.js +67 -0
- package/lib/module/Shared/Libs/sendDiagnosticReport.js +130 -0
- package/lib/module/Shared/Services/DataUploadService.js +6 -3
- package/lib/module/Shared/Types/documentData.js +1 -0
- package/lib/module/Shared/Types/documentReadResult.js +3 -0
- package/lib/module/Translation/Resources/en.js +5 -0
- package/lib/module/Translation/Resources/tr.js +5 -0
- package/lib/module/Trustchex.js +31 -4
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts +20 -0
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +5 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts +2 -0
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts +35 -0
- package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts +48 -0
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +145 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts +12 -0
- package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts +33 -0
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts +3 -0
- package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts +33 -0
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts +30 -0
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +3 -2
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/documentData.d.ts +44 -0
- package/lib/typescript/src/Shared/Types/documentData.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/documentReadResult.d.ts +44 -0
- package/lib/typescript/src/Shared/Types/documentReadResult.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts +1 -1
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +5 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +5 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +9 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +5 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +0 -4
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +23 -0
- package/src/Screens/Dynamic/VideoCallScreen.tsx +5 -1
- package/src/Screens/Static/ResultScreen.tsx +85 -16
- package/src/Shared/Components/DiagnosticReportButton.tsx +77 -0
- package/src/Shared/Components/EIDScanner.tsx +67 -18
- package/src/Shared/Components/IdentityDocumentCamera.tsx +145 -7
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +6 -0
- package/src/Shared/Components/NavigationManager.tsx +7 -7
- package/src/Shared/Components/TrustchexCamera.tsx +6 -0
- package/src/Shared/Contexts/AppContext.ts +8 -0
- package/src/Shared/EIDReader/eidReader.ts +193 -47
- package/src/Shared/EIDReader/lds/icao/dg11File.ts +94 -0
- package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
- package/src/Shared/Libs/diagnosticReport.ts +206 -0
- package/src/Shared/Libs/diagnostics.ts +251 -0
- package/src/Shared/Libs/documentDataNormalizer.ts +108 -0
- package/src/Shared/Libs/mrz.utils.ts +0 -5
- package/src/Shared/Libs/mrzFrameAggregator.ts +61 -3
- package/src/Shared/Libs/mrzTransliteration.ts +131 -0
- package/src/Shared/Libs/native-device-info.utils.ts +116 -0
- package/src/Shared/Libs/sendDiagnosticReport.ts +165 -0
- package/src/Shared/Services/DataUploadService.ts +9 -3
- package/src/Shared/Types/documentData.ts +52 -0
- package/src/Shared/Types/documentReadResult.ts +47 -0
- package/src/Shared/Types/mrzFields.ts +0 -5
- package/src/Translation/Resources/en.ts +6 -0
- package/src/Translation/Resources/tr.ts +6 -0
- package/src/Trustchex.tsx +48 -3
- package/src/index.tsx +7 -0
- package/src/version.ts +1 -1
|
@@ -28,6 +28,7 @@ import { useKeepAwake } from '../Libs/native-keep-awake.utils';
|
|
|
28
28
|
import { useIsFocused } from '@react-navigation/native';
|
|
29
29
|
import { useTranslation } from 'react-i18next';
|
|
30
30
|
import { debugLog, logError, isDebugEnabled } from '../Libs/debug.utils';
|
|
31
|
+
import { diagnostics } from '../Libs/diagnostics';
|
|
31
32
|
import LottieView from 'lottie-react-native';
|
|
32
33
|
import StyledButton from './StyledButton';
|
|
33
34
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -112,12 +113,25 @@ const IdentityDocumentCamera = ({
|
|
|
112
113
|
const [currentFaceImage, setCurrentFaceImage] = useState<string | undefined>(
|
|
113
114
|
undefined
|
|
114
115
|
);
|
|
116
|
+
// Synchronous mirror of the locked face image (see currentHologramImageRef).
|
|
117
|
+
// Used as a fallback when completing SCAN_HOLOGRAM: the torch is on during
|
|
118
|
+
// hologram capture so the live frame's face is often rejected, leaving the
|
|
119
|
+
// state-derived faceImageToUse null within the stale closure — without this
|
|
120
|
+
// the completion gate's `&& !!faceImageToUse` would never pass.
|
|
121
|
+
const currentFaceImageRef = useRef<string | undefined>(undefined);
|
|
115
122
|
const [_currentHologramMaskImage, setCurrentHologramMaskImage] = useState<
|
|
116
123
|
string | undefined
|
|
117
124
|
>(undefined);
|
|
118
125
|
const [currentHologramImage, setCurrentHologramImage] = useState<
|
|
119
126
|
string | undefined
|
|
120
127
|
>(undefined);
|
|
128
|
+
// Mirror of currentHologramImage for synchronous reads inside the per-frame
|
|
129
|
+
// callback. The setState above is async and currentHologramImage is a
|
|
130
|
+
// useCallback dependency, so frames processed before React re-renders the
|
|
131
|
+
// closure would otherwise still see the old (undefined) value — causing the
|
|
132
|
+
// SCAN_HOLOGRAM step to re-collect faces forever and never complete after a
|
|
133
|
+
// successful detection. Always keep this ref in lockstep with the state.
|
|
134
|
+
const currentHologramImageRef = useRef<string | undefined>(undefined);
|
|
121
135
|
const [currentSecondaryFaceImage, setCurrentSecondaryFaceImage] = useState<
|
|
122
136
|
string | undefined
|
|
123
137
|
>(undefined);
|
|
@@ -188,10 +202,30 @@ const IdentityDocumentCamera = ({
|
|
|
188
202
|
const isHologramDetectionInProgress = useRef(false); // Prevent concurrent hologram detection calls
|
|
189
203
|
const isCompletionCallbackInvoked = useRef(false); // Prevent multiple callback invocations when COMPLETED
|
|
190
204
|
const isStepTransitionInProgress = useRef(false); // Prevent duplicate step transitions from rapid frames
|
|
205
|
+
// The step a transition is currently in progress FROM. The latch above is
|
|
206
|
+
// reset asynchronously (via a nextStep effect), so in fast flows (e.g. demo
|
|
207
|
+
// mode auto-advancing) a later transition from a DIFFERENT step could be
|
|
208
|
+
// wrongly blocked while the latch is still set for the previous step. Gating
|
|
209
|
+
// on the originating step lets a genuine next-step transition through while
|
|
210
|
+
// still de-duping rapid repeat frames of the SAME transition.
|
|
211
|
+
const stepTransitionFrom = useRef<
|
|
212
|
+
'SCAN_ID_FRONT_OR_PASSPORT' | 'SCAN_ID_BACK' | 'SCAN_HOLOGRAM' | null
|
|
213
|
+
>(null);
|
|
191
214
|
const isCompletionAnnouncementSpoken = useRef(false); // Prevent duplicate "scan completed" voice guidance
|
|
192
215
|
|
|
193
216
|
const faceDetectionErrorCount = useRef(0);
|
|
194
217
|
const brightnessHistory = useRef<number[]>([]);
|
|
218
|
+
// Running camera-quality stats for the support diagnostic report.
|
|
219
|
+
const diagStats = useRef({
|
|
220
|
+
frames: 0,
|
|
221
|
+
brightnessSum: 0,
|
|
222
|
+
minBrightness: 255,
|
|
223
|
+
maxBrightness: 0,
|
|
224
|
+
frameW: 0,
|
|
225
|
+
frameH: 0,
|
|
226
|
+
hadLowBrightness: false,
|
|
227
|
+
hadBlurryFrames: false,
|
|
228
|
+
});
|
|
195
229
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
196
230
|
const faceImages = useRef<string[]>([]);
|
|
197
231
|
const hologramImageCountRef = useRef(0);
|
|
@@ -277,6 +311,7 @@ const IdentityDocumentCamera = ({
|
|
|
277
311
|
setIsActive(true);
|
|
278
312
|
isCompletionCallbackInvoked.current = false; // Reset callback flag when starting new scan
|
|
279
313
|
isStepTransitionInProgress.current = false;
|
|
314
|
+
stepTransitionFrom.current = null;
|
|
280
315
|
isCompletionAnnouncementSpoken.current = false;
|
|
281
316
|
} else {
|
|
282
317
|
setIsActive(false);
|
|
@@ -292,12 +327,27 @@ const IdentityDocumentCamera = ({
|
|
|
292
327
|
validMRZConsecutiveCount.current = 0;
|
|
293
328
|
mrzAggregator.current.reset();
|
|
294
329
|
setMrzReliable(false);
|
|
330
|
+
// Start a fresh diagnostics session for the support report.
|
|
331
|
+
diagnostics.reset(Date.now());
|
|
332
|
+
diagStats.current = {
|
|
333
|
+
frames: 0,
|
|
334
|
+
brightnessSum: 0,
|
|
335
|
+
minBrightness: 255,
|
|
336
|
+
maxBrightness: 0,
|
|
337
|
+
frameW: 0,
|
|
338
|
+
frameH: 0,
|
|
339
|
+
hadLowBrightness: false,
|
|
340
|
+
hadBlurryFrames: false,
|
|
341
|
+
};
|
|
295
342
|
cachedBarcode.current = null;
|
|
296
343
|
isCompletionCallbackInvoked.current = false;
|
|
297
344
|
isStepTransitionInProgress.current = false;
|
|
345
|
+
stepTransitionFrom.current = null;
|
|
298
346
|
isCompletionAnnouncementSpoken.current = false;
|
|
299
347
|
// Clear all captured image states from previous scan
|
|
348
|
+
currentFaceImageRef.current = undefined;
|
|
300
349
|
setCurrentFaceImage(undefined);
|
|
350
|
+
currentHologramImageRef.current = undefined;
|
|
301
351
|
setCurrentHologramImage(undefined);
|
|
302
352
|
setCurrentSecondaryFaceImage(undefined);
|
|
303
353
|
resetLastMessage();
|
|
@@ -311,9 +361,12 @@ const IdentityDocumentCamera = ({
|
|
|
311
361
|
setLatestHologramFaceImage(undefined);
|
|
312
362
|
isCompletionCallbackInvoked.current = false; // Reset callback flag on unmount
|
|
313
363
|
isStepTransitionInProgress.current = false;
|
|
364
|
+
stepTransitionFrom.current = null;
|
|
314
365
|
isCompletionAnnouncementSpoken.current = false;
|
|
315
366
|
// Clear all captured image states on unmount
|
|
367
|
+
currentFaceImageRef.current = undefined;
|
|
316
368
|
setCurrentFaceImage(undefined);
|
|
369
|
+
currentHologramImageRef.current = undefined;
|
|
317
370
|
setCurrentHologramImage(undefined);
|
|
318
371
|
setCurrentSecondaryFaceImage(undefined);
|
|
319
372
|
resetLastMessage();
|
|
@@ -387,6 +440,7 @@ const IdentityDocumentCamera = ({
|
|
|
387
440
|
isCompletionCallbackInvoked.current = false;
|
|
388
441
|
}
|
|
389
442
|
isStepTransitionInProgress.current = false;
|
|
443
|
+
stepTransitionFrom.current = null;
|
|
390
444
|
}, [nextStep]);
|
|
391
445
|
|
|
392
446
|
// Update status bar based on guide visibility
|
|
@@ -587,6 +641,7 @@ const IdentityDocumentCamera = ({
|
|
|
587
641
|
setHologramImageCount(0);
|
|
588
642
|
setLatestHologramFaceImage(undefined);
|
|
589
643
|
// Clear previous hologram state to prevent premature completion
|
|
644
|
+
currentHologramImageRef.current = undefined;
|
|
590
645
|
setCurrentHologramImage(undefined);
|
|
591
646
|
setCurrentHologramMaskImage(undefined);
|
|
592
647
|
}
|
|
@@ -643,11 +698,18 @@ const IdentityDocumentCamera = ({
|
|
|
643
698
|
fromStep: 'SCAN_ID_FRONT_OR_PASSPORT' | 'SCAN_ID_BACK' | 'SCAN_HOLOGRAM',
|
|
644
699
|
scannedData: DocumentScannedData
|
|
645
700
|
) => {
|
|
646
|
-
|
|
701
|
+
// De-dupe only rapid repeat frames of the SAME originating step. A
|
|
702
|
+
// transition from a different step is a genuine advance (the async latch
|
|
703
|
+
// reset may not have run yet in fast flows like demo mode), so allow it.
|
|
704
|
+
if (
|
|
705
|
+
isStepTransitionInProgress.current &&
|
|
706
|
+
stepTransitionFrom.current === fromStep
|
|
707
|
+
) {
|
|
647
708
|
return;
|
|
648
709
|
}
|
|
649
710
|
|
|
650
711
|
isStepTransitionInProgress.current = true;
|
|
712
|
+
stepTransitionFrom.current = fromStep;
|
|
651
713
|
|
|
652
714
|
// Torch only needed during SCAN_HOLOGRAM - turn off for all other transitions
|
|
653
715
|
if (nextStepType !== 'SCAN_HOLOGRAM') {
|
|
@@ -880,9 +942,32 @@ const IdentityDocumentCamera = ({
|
|
|
880
942
|
)
|
|
881
943
|
: 0;
|
|
882
944
|
const frameWeight = mrzBandHeight > 0 ? mrzBandHeight : 1;
|
|
945
|
+
// ML Kit per-symbol confidence over MRZ characters (Android bundled library
|
|
946
|
+
// only; undefined on iOS). When present it down-weights frames the
|
|
947
|
+
// recognizer was unsure about, so a confident frame outvotes a shaky one of
|
|
948
|
+
// the same band height on look-alike ties (A↔4, S↔5, B↔8).
|
|
949
|
+
const frameConfidence = scannedText?.textConfidence?.mean;
|
|
883
950
|
const consensus = mrzAggregator.current.addFrame({
|
|
884
951
|
text: textForValidation,
|
|
885
952
|
weight: frameWeight,
|
|
953
|
+
confidence: frameConfidence,
|
|
954
|
+
});
|
|
955
|
+
|
|
956
|
+
// Record MRZ consensus metrics for the support diagnostic report (counts
|
|
957
|
+
// and pass/fail only — never the MRZ values themselves). `reachedStable`
|
|
958
|
+
// is sticky in the collector, so a later flicker can't unset it.
|
|
959
|
+
// `stabilityFrames`/`minContestedMargin` and the MRZ band pixel height are
|
|
960
|
+
// device-variance signals (convergence speed, look-alike resolution
|
|
961
|
+
// confidence, and whether the band cleared the ~16px/char OCR floor).
|
|
962
|
+
diagnostics.setMrz({
|
|
963
|
+
framesProcessed: consensus.frames,
|
|
964
|
+
reachedStable: consensus.stable,
|
|
965
|
+
format: consensus.validation?.format ?? undefined,
|
|
966
|
+
stabilityFrames: consensus.stableStreak,
|
|
967
|
+
minContestedMargin: Number.isFinite(consensus.minContestedMargin)
|
|
968
|
+
? consensus.minContestedMargin
|
|
969
|
+
: 0,
|
|
970
|
+
maxMrzBandHeightPx: mrzBandHeight,
|
|
886
971
|
});
|
|
887
972
|
|
|
888
973
|
// Use the fused consensus when it is valid; otherwise fall back to a
|
|
@@ -922,6 +1007,9 @@ const IdentityDocumentCamera = ({
|
|
|
922
1007
|
? 'PASSPORT'
|
|
923
1008
|
: detectedDocumentType;
|
|
924
1009
|
|
|
1010
|
+
// Record the detected document type for the diagnostic report.
|
|
1011
|
+
if (documentType) diagnostics.setDocument(documentType);
|
|
1012
|
+
|
|
925
1013
|
// Crop faces once document type is confirmed or we're past the initial step
|
|
926
1014
|
const shouldCropFaces =
|
|
927
1015
|
documentType === 'ID_FRONT' ||
|
|
@@ -1005,6 +1093,7 @@ const IdentityDocumentCamera = ({
|
|
|
1005
1093
|
// Continue scanning without locking this face
|
|
1006
1094
|
} else {
|
|
1007
1095
|
faceImageToUse = croppedFaces[0];
|
|
1096
|
+
currentFaceImageRef.current = croppedFaces[0];
|
|
1008
1097
|
setCurrentFaceImage(croppedFaces[0]);
|
|
1009
1098
|
}
|
|
1010
1099
|
}
|
|
@@ -1304,8 +1393,8 @@ const IdentityDocumentCamera = ({
|
|
|
1304
1393
|
if (primaryFaceOnly) {
|
|
1305
1394
|
hologramFramesWithoutFace.current = 0;
|
|
1306
1395
|
|
|
1307
|
-
if (
|
|
1308
|
-
scannedData.hologramImage =
|
|
1396
|
+
if (currentHologramImageRef.current) {
|
|
1397
|
+
scannedData.hologramImage = currentHologramImageRef.current;
|
|
1309
1398
|
} else if (faceImages.current.length < HOLOGRAM_IMAGE_COUNT) {
|
|
1310
1399
|
// Space out captures for better variation
|
|
1311
1400
|
const timeSinceLastCapture =
|
|
@@ -1392,6 +1481,7 @@ const IdentityDocumentCamera = ({
|
|
|
1392
1481
|
if (hologram) {
|
|
1393
1482
|
setCurrentHologramMaskImage(hologramMask);
|
|
1394
1483
|
scannedData.hologramImage = hologram;
|
|
1484
|
+
currentHologramImageRef.current = hologram;
|
|
1395
1485
|
setCurrentHologramImage(hologram);
|
|
1396
1486
|
if (isDebugEnabled()) {
|
|
1397
1487
|
debugLog(
|
|
@@ -1423,8 +1513,8 @@ const IdentityDocumentCamera = ({
|
|
|
1423
1513
|
} else {
|
|
1424
1514
|
hologramFramesWithoutFace.current++;
|
|
1425
1515
|
}
|
|
1426
|
-
} else if (
|
|
1427
|
-
scannedData.hologramImage =
|
|
1516
|
+
} else if (currentHologramImageRef.current) {
|
|
1517
|
+
scannedData.hologramImage = currentHologramImageRef.current;
|
|
1428
1518
|
}
|
|
1429
1519
|
|
|
1430
1520
|
// Secondary face capture (continuous during initial scan and hologram detection)
|
|
@@ -1637,13 +1727,23 @@ const IdentityDocumentCamera = ({
|
|
|
1637
1727
|
return;
|
|
1638
1728
|
}
|
|
1639
1729
|
|
|
1640
|
-
// Complete when hologram captured OR retries exhausted (not mid-collection)
|
|
1730
|
+
// Complete when hologram captured OR retries exhausted (not mid-collection).
|
|
1731
|
+
// Fall back to the previously locked face (ref) when the live frame's face
|
|
1732
|
+
// was rejected — the torch is on during hologram capture, so faceImageToUse
|
|
1733
|
+
// is frequently null even though a valid face was locked during the front
|
|
1734
|
+
// scan. Without this the gate's face requirement could never be satisfied.
|
|
1735
|
+
const faceForCompletion = faceImageToUse ?? currentFaceImageRef.current;
|
|
1641
1736
|
const stepComplete =
|
|
1642
1737
|
(!!scannedData.hologramImage ||
|
|
1643
1738
|
(maxRetriesReached && !isCollecting)) &&
|
|
1644
|
-
!!
|
|
1739
|
+
!!faceForCompletion; // Require face before completing
|
|
1645
1740
|
|
|
1646
1741
|
if (stepComplete) {
|
|
1742
|
+
// Make sure the locked face rides along even if this frame's face was
|
|
1743
|
+
// rejected.
|
|
1744
|
+
if (!scannedData.faceImage && faceForCompletion) {
|
|
1745
|
+
scannedData.faceImage = faceForCompletion;
|
|
1746
|
+
}
|
|
1647
1747
|
// Ensure preserved MRZ data is included (current frame may not have
|
|
1648
1748
|
// readable MRZ due to flash/tilting during hologram capture)
|
|
1649
1749
|
if (!scannedData.mrzText && lastValidMRZText.current) {
|
|
@@ -1846,6 +1946,21 @@ const IdentityDocumentCamera = ({
|
|
|
1846
1946
|
const avgBrightness =
|
|
1847
1947
|
brightnessHistory.current.reduce((a, b) => a + b, 0) /
|
|
1848
1948
|
brightnessHistory.current.length;
|
|
1949
|
+
|
|
1950
|
+
// Accumulate camera diagnostics (frame count, brightness range, dims) for
|
|
1951
|
+
// the support diagnostic report. Cheap, side-effect-only.
|
|
1952
|
+
diagStats.current.frames += 1;
|
|
1953
|
+
diagStats.current.brightnessSum += frameBrightness;
|
|
1954
|
+
diagStats.current.minBrightness = Math.min(
|
|
1955
|
+
diagStats.current.minBrightness,
|
|
1956
|
+
frameBrightness
|
|
1957
|
+
);
|
|
1958
|
+
diagStats.current.maxBrightness = Math.max(
|
|
1959
|
+
diagStats.current.maxBrightness,
|
|
1960
|
+
frameBrightness
|
|
1961
|
+
);
|
|
1962
|
+
diagStats.current.frameW = frame.width ?? diagStats.current.frameW;
|
|
1963
|
+
diagStats.current.frameH = frame.height ?? diagStats.current.frameH;
|
|
1849
1964
|
const isOverallBright = avgBrightness >= MIN_BRIGHTNESS_THRESHOLD;
|
|
1850
1965
|
|
|
1851
1966
|
// Check brightness in center region (area of interest)
|
|
@@ -1864,6 +1979,7 @@ const IdentityDocumentCamera = ({
|
|
|
1864
1979
|
}
|
|
1865
1980
|
|
|
1866
1981
|
setIsBrightnessLow(!isRegionBright);
|
|
1982
|
+
if (!isRegionBright) diagStats.current.hadLowBrightness = true;
|
|
1867
1983
|
|
|
1868
1984
|
// Check blur only in center region (area of interest) to avoid false positives
|
|
1869
1985
|
// from iOS depth-of-field background blur
|
|
@@ -1882,10 +1998,31 @@ const IdentityDocumentCamera = ({
|
|
|
1882
1998
|
);
|
|
1883
1999
|
isNotBlurry = !isBlurry;
|
|
1884
2000
|
setIsFrameBlurry(isBlurry);
|
|
2001
|
+
if (isBlurry) diagStats.current.hadBlurryFrames = true;
|
|
1885
2002
|
} catch (error) {
|
|
1886
2003
|
setIsFrameBlurry(false);
|
|
1887
2004
|
}
|
|
1888
2005
|
|
|
2006
|
+
// Flush accumulated camera stats into the diagnostics collector.
|
|
2007
|
+
const ds = diagStats.current;
|
|
2008
|
+
diagnostics.setCamera({
|
|
2009
|
+
frames: ds.frames,
|
|
2010
|
+
frameW: ds.frameW,
|
|
2011
|
+
frameH: ds.frameH,
|
|
2012
|
+
avgBrightness:
|
|
2013
|
+
ds.frames > 0 ? Math.round(ds.brightnessSum / ds.frames) : 0,
|
|
2014
|
+
minBrightness: ds.minBrightness === 255 && ds.frames === 0 ? 0 : ds.minBrightness,
|
|
2015
|
+
maxBrightness: ds.maxBrightness,
|
|
2016
|
+
hadLowBrightness: ds.hadLowBrightness,
|
|
2017
|
+
hadBlurryFrames: ds.hadBlurryFrames,
|
|
2018
|
+
// The actual gates the measured values are judged against — lets support
|
|
2019
|
+
// read "measured 45 vs floor 45" instead of just a pass/fail flag.
|
|
2020
|
+
thresholds: {
|
|
2021
|
+
minBrightness: MIN_BRIGHTNESS_THRESHOLD,
|
|
2022
|
+
blur: 50,
|
|
2023
|
+
},
|
|
2024
|
+
});
|
|
2025
|
+
|
|
1889
2026
|
// Only proceed if image quality is acceptable
|
|
1890
2027
|
const hasAcceptableQuality = isRegionBright && isNotBlurry;
|
|
1891
2028
|
|
|
@@ -1934,6 +2071,7 @@ const IdentityDocumentCamera = ({
|
|
|
1934
2071
|
const scannedText: BlockText = {
|
|
1935
2072
|
resultText,
|
|
1936
2073
|
mrzOnlyText: undefined, // Will be set below if MRZ blocks detected
|
|
2074
|
+
textConfidence: frame.textConfidence,
|
|
1937
2075
|
blocks: textBlocks.map((block) => ({
|
|
1938
2076
|
blockText: block.text || '',
|
|
1939
2077
|
blockFrame: block.blockFrame ?? {
|
|
@@ -30,6 +30,12 @@ export type BlockText = {
|
|
|
30
30
|
blocks: BlocksData[];
|
|
31
31
|
resultText: string;
|
|
32
32
|
mrzOnlyText?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Per-frame OCR confidence over MRZ characters from ML Kit per-symbol
|
|
35
|
+
* confidence (Android bundled library only; undefined on iOS). Feeds the
|
|
36
|
+
* multi-frame vote weight so low-confidence frames count less.
|
|
37
|
+
*/
|
|
38
|
+
textConfidence?: { mean: number; min: number; count: number };
|
|
33
39
|
};
|
|
34
40
|
|
|
35
41
|
export type BlocksData = {
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from '@react-navigation/native';
|
|
14
14
|
import { View, StyleSheet, Alert } from 'react-native';
|
|
15
15
|
import { useTranslation } from 'react-i18next';
|
|
16
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
17
16
|
import i18n from '../../Translation';
|
|
18
17
|
import StyledButton from './StyledButton';
|
|
19
18
|
import { analyticsService } from '../Services/AnalyticsService';
|
|
@@ -37,7 +36,6 @@ const NavigationManager = forwardRef(
|
|
|
37
36
|
const appContext = useContext(AppContext);
|
|
38
37
|
const navigation = useNavigation();
|
|
39
38
|
const { t } = useTranslation();
|
|
40
|
-
const insets = useSafeAreaInsets();
|
|
41
39
|
|
|
42
40
|
const routes = {
|
|
43
41
|
VERIFICATION_SESSION_CHECK: 'VerificationSessionCheckScreen',
|
|
@@ -274,7 +272,7 @@ const NavigationManager = forwardRef(
|
|
|
274
272
|
return (
|
|
275
273
|
appContext.currentWorkflowStep &&
|
|
276
274
|
(!appContext.currentWorkflowStep?.required || canSkipStep) && (
|
|
277
|
-
<View style={
|
|
275
|
+
<View style={styles.container}>
|
|
278
276
|
<StyledButton mode="text" onPress={goToNextRouteWithAlert}>
|
|
279
277
|
{t('navigationManager.skipStepLabel')}
|
|
280
278
|
</StyledButton>
|
|
@@ -285,11 +283,13 @@ const NavigationManager = forwardRef(
|
|
|
285
283
|
);
|
|
286
284
|
|
|
287
285
|
const styles = StyleSheet.create({
|
|
286
|
+
// Hug the text button: centered, minimal padding, no full-width opaque bar so
|
|
287
|
+
// it occupies the least area and never overlays sibling content. The screen's
|
|
288
|
+
// footer wrapper already applies the bottom safe-area inset.
|
|
288
289
|
container: {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
backgroundColor: 'white',
|
|
290
|
+
alignSelf: 'center',
|
|
291
|
+
paddingHorizontal: 16,
|
|
292
|
+
paddingVertical: 8,
|
|
293
293
|
},
|
|
294
294
|
});
|
|
295
295
|
|
|
@@ -62,6 +62,12 @@ export interface Frame {
|
|
|
62
62
|
faces?: NativeFace[];
|
|
63
63
|
textBlocks?: NativeTextBlock[];
|
|
64
64
|
resultText?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Per-frame OCR confidence over MRZ characters, from ML Kit per-symbol
|
|
67
|
+
* confidence. Android (bundled ML Kit library) only — iOS ML Kit exposes no
|
|
68
|
+
* symbol-level confidence, so this is undefined there.
|
|
69
|
+
*/
|
|
70
|
+
textConfidence?: { mean: number; min: number; count: number };
|
|
65
71
|
barcodes?: NativeBarcode[];
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
IdentificationInfo,
|
|
4
4
|
WorkflowStep,
|
|
5
5
|
} from '../Types/identificationInfo';
|
|
6
|
+
import type { DocumentReadResult } from '../Types/documentReadResult';
|
|
6
7
|
|
|
7
8
|
export type AppContextType = {
|
|
8
9
|
isDemoSession: boolean;
|
|
@@ -19,8 +20,12 @@ export type AppContextType = {
|
|
|
19
20
|
currentWorkflowStep?: WorkflowStep;
|
|
20
21
|
isDebugNavigated?: boolean;
|
|
21
22
|
isTestVideoSession?: boolean;
|
|
23
|
+
/** Last document read result — available to upload services for real-name fields. */
|
|
24
|
+
lastDocumentRead?: DocumentReadResult;
|
|
22
25
|
onCompleted?: () => void;
|
|
23
26
|
onError?: (error: string) => void;
|
|
27
|
+
onDocumentRead?: (result: DocumentReadResult) => void;
|
|
28
|
+
setLastDocumentRead?: (result: DocumentReadResult) => void;
|
|
24
29
|
setSessionId?: (id: string) => void;
|
|
25
30
|
setBaseUrl?: (url: string) => void;
|
|
26
31
|
setIsDemoSession?: (isDemoSession: boolean) => void;
|
|
@@ -49,8 +54,11 @@ export default createContext<AppContextType>({
|
|
|
49
54
|
currentWorkflowStep: undefined,
|
|
50
55
|
isDebugNavigated: false,
|
|
51
56
|
isTestVideoSession: false,
|
|
57
|
+
lastDocumentRead: undefined,
|
|
52
58
|
onCompleted: undefined,
|
|
53
59
|
onError: undefined,
|
|
60
|
+
onDocumentRead: undefined,
|
|
61
|
+
setLastDocumentRead: undefined,
|
|
54
62
|
setSessionId: undefined,
|
|
55
63
|
setBaseUrl: undefined,
|
|
56
64
|
});
|