@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
|
@@ -13,6 +13,7 @@ import { useKeepAwake } from "../Libs/native-keep-awake.utils.js";
|
|
|
13
13
|
import { useIsFocused } from '@react-navigation/native';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
import { debugLog, logError, isDebugEnabled } from "../Libs/debug.utils.js";
|
|
16
|
+
import { diagnostics } from "../Libs/diagnostics.js";
|
|
16
17
|
import LottieView from 'lottie-react-native';
|
|
17
18
|
import StyledButton from "./StyledButton.js";
|
|
18
19
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -52,8 +53,21 @@ const IdentityDocumentCamera = ({
|
|
|
52
53
|
}, []);
|
|
53
54
|
const isCameraInitialized = useRef(false);
|
|
54
55
|
const [currentFaceImage, setCurrentFaceImage] = useState(undefined);
|
|
56
|
+
// Synchronous mirror of the locked face image (see currentHologramImageRef).
|
|
57
|
+
// Used as a fallback when completing SCAN_HOLOGRAM: the torch is on during
|
|
58
|
+
// hologram capture so the live frame's face is often rejected, leaving the
|
|
59
|
+
// state-derived faceImageToUse null within the stale closure — without this
|
|
60
|
+
// the completion gate's `&& !!faceImageToUse` would never pass.
|
|
61
|
+
const currentFaceImageRef = useRef(undefined);
|
|
55
62
|
const [_currentHologramMaskImage, setCurrentHologramMaskImage] = useState(undefined);
|
|
56
63
|
const [currentHologramImage, setCurrentHologramImage] = useState(undefined);
|
|
64
|
+
// Mirror of currentHologramImage for synchronous reads inside the per-frame
|
|
65
|
+
// callback. The setState above is async and currentHologramImage is a
|
|
66
|
+
// useCallback dependency, so frames processed before React re-renders the
|
|
67
|
+
// closure would otherwise still see the old (undefined) value — causing the
|
|
68
|
+
// SCAN_HOLOGRAM step to re-collect faces forever and never complete after a
|
|
69
|
+
// successful detection. Always keep this ref in lockstep with the state.
|
|
70
|
+
const currentHologramImageRef = useRef(undefined);
|
|
57
71
|
const [currentSecondaryFaceImage, setCurrentSecondaryFaceImage] = useState(undefined);
|
|
58
72
|
const [isBrightnessLow, setIsBrightnessLow] = useState(false);
|
|
59
73
|
const [isFrameBlurry, setIsFrameBlurry] = useState(false);
|
|
@@ -110,10 +124,28 @@ const IdentityDocumentCamera = ({
|
|
|
110
124
|
const isHologramDetectionInProgress = useRef(false); // Prevent concurrent hologram detection calls
|
|
111
125
|
const isCompletionCallbackInvoked = useRef(false); // Prevent multiple callback invocations when COMPLETED
|
|
112
126
|
const isStepTransitionInProgress = useRef(false); // Prevent duplicate step transitions from rapid frames
|
|
127
|
+
// The step a transition is currently in progress FROM. The latch above is
|
|
128
|
+
// reset asynchronously (via a nextStep effect), so in fast flows (e.g. demo
|
|
129
|
+
// mode auto-advancing) a later transition from a DIFFERENT step could be
|
|
130
|
+
// wrongly blocked while the latch is still set for the previous step. Gating
|
|
131
|
+
// on the originating step lets a genuine next-step transition through while
|
|
132
|
+
// still de-duping rapid repeat frames of the SAME transition.
|
|
133
|
+
const stepTransitionFrom = useRef(null);
|
|
113
134
|
const isCompletionAnnouncementSpoken = useRef(false); // Prevent duplicate "scan completed" voice guidance
|
|
114
135
|
|
|
115
136
|
const faceDetectionErrorCount = useRef(0);
|
|
116
137
|
const brightnessHistory = useRef([]);
|
|
138
|
+
// Running camera-quality stats for the support diagnostic report.
|
|
139
|
+
const diagStats = useRef({
|
|
140
|
+
frames: 0,
|
|
141
|
+
brightnessSum: 0,
|
|
142
|
+
minBrightness: 255,
|
|
143
|
+
maxBrightness: 0,
|
|
144
|
+
frameW: 0,
|
|
145
|
+
frameH: 0,
|
|
146
|
+
hadLowBrightness: false,
|
|
147
|
+
hadBlurryFrames: false
|
|
148
|
+
});
|
|
117
149
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
118
150
|
const faceImages = useRef([]);
|
|
119
151
|
const hologramImageCountRef = useRef(0);
|
|
@@ -151,6 +183,7 @@ const IdentityDocumentCamera = ({
|
|
|
151
183
|
setIsActive(true);
|
|
152
184
|
isCompletionCallbackInvoked.current = false; // Reset callback flag when starting new scan
|
|
153
185
|
isStepTransitionInProgress.current = false;
|
|
186
|
+
stepTransitionFrom.current = null;
|
|
154
187
|
isCompletionAnnouncementSpoken.current = false;
|
|
155
188
|
} else {
|
|
156
189
|
setIsActive(false);
|
|
@@ -166,12 +199,27 @@ const IdentityDocumentCamera = ({
|
|
|
166
199
|
validMRZConsecutiveCount.current = 0;
|
|
167
200
|
mrzAggregator.current.reset();
|
|
168
201
|
setMrzReliable(false);
|
|
202
|
+
// Start a fresh diagnostics session for the support report.
|
|
203
|
+
diagnostics.reset(Date.now());
|
|
204
|
+
diagStats.current = {
|
|
205
|
+
frames: 0,
|
|
206
|
+
brightnessSum: 0,
|
|
207
|
+
minBrightness: 255,
|
|
208
|
+
maxBrightness: 0,
|
|
209
|
+
frameW: 0,
|
|
210
|
+
frameH: 0,
|
|
211
|
+
hadLowBrightness: false,
|
|
212
|
+
hadBlurryFrames: false
|
|
213
|
+
};
|
|
169
214
|
cachedBarcode.current = null;
|
|
170
215
|
isCompletionCallbackInvoked.current = false;
|
|
171
216
|
isStepTransitionInProgress.current = false;
|
|
217
|
+
stepTransitionFrom.current = null;
|
|
172
218
|
isCompletionAnnouncementSpoken.current = false;
|
|
173
219
|
// Clear all captured image states from previous scan
|
|
220
|
+
currentFaceImageRef.current = undefined;
|
|
174
221
|
setCurrentFaceImage(undefined);
|
|
222
|
+
currentHologramImageRef.current = undefined;
|
|
175
223
|
setCurrentHologramImage(undefined);
|
|
176
224
|
setCurrentSecondaryFaceImage(undefined);
|
|
177
225
|
resetLastMessage();
|
|
@@ -184,9 +232,12 @@ const IdentityDocumentCamera = ({
|
|
|
184
232
|
setLatestHologramFaceImage(undefined);
|
|
185
233
|
isCompletionCallbackInvoked.current = false; // Reset callback flag on unmount
|
|
186
234
|
isStepTransitionInProgress.current = false;
|
|
235
|
+
stepTransitionFrom.current = null;
|
|
187
236
|
isCompletionAnnouncementSpoken.current = false;
|
|
188
237
|
// Clear all captured image states on unmount
|
|
238
|
+
currentFaceImageRef.current = undefined;
|
|
189
239
|
setCurrentFaceImage(undefined);
|
|
240
|
+
currentHologramImageRef.current = undefined;
|
|
190
241
|
setCurrentHologramImage(undefined);
|
|
191
242
|
setCurrentSecondaryFaceImage(undefined);
|
|
192
243
|
resetLastMessage();
|
|
@@ -232,6 +283,7 @@ const IdentityDocumentCamera = ({
|
|
|
232
283
|
isCompletionCallbackInvoked.current = false;
|
|
233
284
|
}
|
|
234
285
|
isStepTransitionInProgress.current = false;
|
|
286
|
+
stepTransitionFrom.current = null;
|
|
235
287
|
}, [nextStep]);
|
|
236
288
|
|
|
237
289
|
// Update status bar based on guide visibility
|
|
@@ -365,6 +417,7 @@ const IdentityDocumentCamera = ({
|
|
|
365
417
|
setHologramImageCount(0);
|
|
366
418
|
setLatestHologramFaceImage(undefined);
|
|
367
419
|
// Clear previous hologram state to prevent premature completion
|
|
420
|
+
currentHologramImageRef.current = undefined;
|
|
368
421
|
setCurrentHologramImage(undefined);
|
|
369
422
|
setCurrentHologramMaskImage(undefined);
|
|
370
423
|
}
|
|
@@ -408,10 +461,14 @@ const IdentityDocumentCamera = ({
|
|
|
408
461
|
}
|
|
409
462
|
}, [setIsTorchOn]);
|
|
410
463
|
const transitionStepWithCallback = useCallback((nextStepType, fromStep, scannedData) => {
|
|
411
|
-
|
|
464
|
+
// De-dupe only rapid repeat frames of the SAME originating step. A
|
|
465
|
+
// transition from a different step is a genuine advance (the async latch
|
|
466
|
+
// reset may not have run yet in fast flows like demo mode), so allow it.
|
|
467
|
+
if (isStepTransitionInProgress.current && stepTransitionFrom.current === fromStep) {
|
|
412
468
|
return;
|
|
413
469
|
}
|
|
414
470
|
isStepTransitionInProgress.current = true;
|
|
471
|
+
stepTransitionFrom.current = fromStep;
|
|
415
472
|
|
|
416
473
|
// Torch only needed during SCAN_HOLOGRAM - turn off for all other transitions
|
|
417
474
|
if (nextStepType !== 'SCAN_HOLOGRAM') {
|
|
@@ -567,9 +624,30 @@ const IdentityDocumentCamera = ({
|
|
|
567
624
|
// of letting one noisy frame reset progress.
|
|
568
625
|
const mrzBandHeight = scannedText?.blocks?.length ? Math.max(0, ...scannedText.blocks.map(b => b.blockFrame?.height ?? 0)) : 0;
|
|
569
626
|
const frameWeight = mrzBandHeight > 0 ? mrzBandHeight : 1;
|
|
627
|
+
// ML Kit per-symbol confidence over MRZ characters (Android bundled library
|
|
628
|
+
// only; undefined on iOS). When present it down-weights frames the
|
|
629
|
+
// recognizer was unsure about, so a confident frame outvotes a shaky one of
|
|
630
|
+
// the same band height on look-alike ties (A↔4, S↔5, B↔8).
|
|
631
|
+
const frameConfidence = scannedText?.textConfidence?.mean;
|
|
570
632
|
const consensus = mrzAggregator.current.addFrame({
|
|
571
633
|
text: textForValidation,
|
|
572
|
-
weight: frameWeight
|
|
634
|
+
weight: frameWeight,
|
|
635
|
+
confidence: frameConfidence
|
|
636
|
+
});
|
|
637
|
+
|
|
638
|
+
// Record MRZ consensus metrics for the support diagnostic report (counts
|
|
639
|
+
// and pass/fail only — never the MRZ values themselves). `reachedStable`
|
|
640
|
+
// is sticky in the collector, so a later flicker can't unset it.
|
|
641
|
+
// `stabilityFrames`/`minContestedMargin` and the MRZ band pixel height are
|
|
642
|
+
// device-variance signals (convergence speed, look-alike resolution
|
|
643
|
+
// confidence, and whether the band cleared the ~16px/char OCR floor).
|
|
644
|
+
diagnostics.setMrz({
|
|
645
|
+
framesProcessed: consensus.frames,
|
|
646
|
+
reachedStable: consensus.stable,
|
|
647
|
+
format: consensus.validation?.format ?? undefined,
|
|
648
|
+
stabilityFrames: consensus.stableStreak,
|
|
649
|
+
minContestedMargin: Number.isFinite(consensus.minContestedMargin) ? consensus.minContestedMargin : 0,
|
|
650
|
+
maxMrzBandHeightPx: mrzBandHeight
|
|
573
651
|
});
|
|
574
652
|
|
|
575
653
|
// Use the fused consensus when it is valid; otherwise fall back to a
|
|
@@ -593,6 +671,9 @@ const IdentityDocumentCamera = ({
|
|
|
593
671
|
// be wrong (ID_FRONT locked before passport MRZ became readable)
|
|
594
672
|
const documentType = nextStep === 'SCAN_ID_FRONT_OR_PASSPORT' ? detectDocumentType(primaryFaces, text, parsedMRZData?.fields, frameWidth, mrzText) : parsedMRZData?.fields?.documentCode === 'P' ? 'PASSPORT' : detectedDocumentType;
|
|
595
673
|
|
|
674
|
+
// Record the detected document type for the diagnostic report.
|
|
675
|
+
if (documentType) diagnostics.setDocument(documentType);
|
|
676
|
+
|
|
596
677
|
// Crop faces once document type is confirmed or we're past the initial step
|
|
597
678
|
const shouldCropFaces = documentType === 'ID_FRONT' || documentType === 'PASSPORT' || nextStep !== 'SCAN_ID_FRONT_OR_PASSPORT';
|
|
598
679
|
const croppedFaces = shouldCropFaces ? await getFaceImages(primaryFaces, image ?? '', frameWidth, frameHeight) : [];
|
|
@@ -646,6 +727,7 @@ const IdentityDocumentCamera = ({
|
|
|
646
727
|
// Continue scanning without locking this face
|
|
647
728
|
} else {
|
|
648
729
|
faceImageToUse = croppedFaces[0];
|
|
730
|
+
currentFaceImageRef.current = croppedFaces[0];
|
|
649
731
|
setCurrentFaceImage(croppedFaces[0]);
|
|
650
732
|
}
|
|
651
733
|
}
|
|
@@ -860,8 +942,8 @@ const IdentityDocumentCamera = ({
|
|
|
860
942
|
// Skip face position validation for hologram — flash toggling causes position jitter
|
|
861
943
|
if (primaryFaceOnly) {
|
|
862
944
|
hologramFramesWithoutFace.current = 0;
|
|
863
|
-
if (
|
|
864
|
-
scannedData.hologramImage =
|
|
945
|
+
if (currentHologramImageRef.current) {
|
|
946
|
+
scannedData.hologramImage = currentHologramImageRef.current;
|
|
865
947
|
} else if (faceImages.current.length < HOLOGRAM_IMAGE_COUNT) {
|
|
866
948
|
// Space out captures for better variation
|
|
867
949
|
const timeSinceLastCapture = Date.now() - lastHologramCaptureTime.current;
|
|
@@ -921,6 +1003,7 @@ const IdentityDocumentCamera = ({
|
|
|
921
1003
|
if (hologram) {
|
|
922
1004
|
setCurrentHologramMaskImage(hologramMask);
|
|
923
1005
|
scannedData.hologramImage = hologram;
|
|
1006
|
+
currentHologramImageRef.current = hologram;
|
|
924
1007
|
setCurrentHologramImage(hologram);
|
|
925
1008
|
if (isDebugEnabled()) {
|
|
926
1009
|
debugLog('IdentityDocumentCamera', '[Hologram] ✓ Saved hologram image');
|
|
@@ -946,8 +1029,8 @@ const IdentityDocumentCamera = ({
|
|
|
946
1029
|
} else {
|
|
947
1030
|
hologramFramesWithoutFace.current++;
|
|
948
1031
|
}
|
|
949
|
-
} else if (
|
|
950
|
-
scannedData.hologramImage =
|
|
1032
|
+
} else if (currentHologramImageRef.current) {
|
|
1033
|
+
scannedData.hologramImage = currentHologramImageRef.current;
|
|
951
1034
|
}
|
|
952
1035
|
|
|
953
1036
|
// Secondary face capture (continuous during initial scan and hologram detection)
|
|
@@ -1081,10 +1164,20 @@ const IdentityDocumentCamera = ({
|
|
|
1081
1164
|
return;
|
|
1082
1165
|
}
|
|
1083
1166
|
|
|
1084
|
-
// Complete when hologram captured OR retries exhausted (not mid-collection)
|
|
1085
|
-
|
|
1167
|
+
// Complete when hologram captured OR retries exhausted (not mid-collection).
|
|
1168
|
+
// Fall back to the previously locked face (ref) when the live frame's face
|
|
1169
|
+
// was rejected — the torch is on during hologram capture, so faceImageToUse
|
|
1170
|
+
// is frequently null even though a valid face was locked during the front
|
|
1171
|
+
// scan. Without this the gate's face requirement could never be satisfied.
|
|
1172
|
+
const faceForCompletion = faceImageToUse ?? currentFaceImageRef.current;
|
|
1173
|
+
const stepComplete = (!!scannedData.hologramImage || maxRetriesReached && !isCollecting) && !!faceForCompletion; // Require face before completing
|
|
1086
1174
|
|
|
1087
1175
|
if (stepComplete) {
|
|
1176
|
+
// Make sure the locked face rides along even if this frame's face was
|
|
1177
|
+
// rejected.
|
|
1178
|
+
if (!scannedData.faceImage && faceForCompletion) {
|
|
1179
|
+
scannedData.faceImage = faceForCompletion;
|
|
1180
|
+
}
|
|
1088
1181
|
// Ensure preserved MRZ data is included (current frame may not have
|
|
1089
1182
|
// readable MRZ due to flash/tilting during hologram capture)
|
|
1090
1183
|
if (!scannedData.mrzText && lastValidMRZText.current) {
|
|
@@ -1196,6 +1289,15 @@ const IdentityDocumentCamera = ({
|
|
|
1196
1289
|
brightnessHistory.current.shift();
|
|
1197
1290
|
}
|
|
1198
1291
|
const avgBrightness = brightnessHistory.current.reduce((a, b) => a + b, 0) / brightnessHistory.current.length;
|
|
1292
|
+
|
|
1293
|
+
// Accumulate camera diagnostics (frame count, brightness range, dims) for
|
|
1294
|
+
// the support diagnostic report. Cheap, side-effect-only.
|
|
1295
|
+
diagStats.current.frames += 1;
|
|
1296
|
+
diagStats.current.brightnessSum += frameBrightness;
|
|
1297
|
+
diagStats.current.minBrightness = Math.min(diagStats.current.minBrightness, frameBrightness);
|
|
1298
|
+
diagStats.current.maxBrightness = Math.max(diagStats.current.maxBrightness, frameBrightness);
|
|
1299
|
+
diagStats.current.frameW = frame.width ?? diagStats.current.frameW;
|
|
1300
|
+
diagStats.current.frameH = frame.height ?? diagStats.current.frameH;
|
|
1199
1301
|
const isOverallBright = avgBrightness >= MIN_BRIGHTNESS_THRESHOLD;
|
|
1200
1302
|
|
|
1201
1303
|
// Check brightness in center region (area of interest)
|
|
@@ -1206,6 +1308,7 @@ const IdentityDocumentCamera = ({
|
|
|
1206
1308
|
isRegionBright = isOverallBright;
|
|
1207
1309
|
}
|
|
1208
1310
|
setIsBrightnessLow(!isRegionBright);
|
|
1311
|
+
if (!isRegionBright) diagStats.current.hadLowBrightness = true;
|
|
1209
1312
|
|
|
1210
1313
|
// Check blur only in center region (area of interest) to avoid false positives
|
|
1211
1314
|
// from iOS depth-of-field background blur
|
|
@@ -1226,10 +1329,30 @@ const IdentityDocumentCamera = ({
|
|
|
1226
1329
|
);
|
|
1227
1330
|
isNotBlurry = !isBlurry;
|
|
1228
1331
|
setIsFrameBlurry(isBlurry);
|
|
1332
|
+
if (isBlurry) diagStats.current.hadBlurryFrames = true;
|
|
1229
1333
|
} catch (error) {
|
|
1230
1334
|
setIsFrameBlurry(false);
|
|
1231
1335
|
}
|
|
1232
1336
|
|
|
1337
|
+
// Flush accumulated camera stats into the diagnostics collector.
|
|
1338
|
+
const ds = diagStats.current;
|
|
1339
|
+
diagnostics.setCamera({
|
|
1340
|
+
frames: ds.frames,
|
|
1341
|
+
frameW: ds.frameW,
|
|
1342
|
+
frameH: ds.frameH,
|
|
1343
|
+
avgBrightness: ds.frames > 0 ? Math.round(ds.brightnessSum / ds.frames) : 0,
|
|
1344
|
+
minBrightness: ds.minBrightness === 255 && ds.frames === 0 ? 0 : ds.minBrightness,
|
|
1345
|
+
maxBrightness: ds.maxBrightness,
|
|
1346
|
+
hadLowBrightness: ds.hadLowBrightness,
|
|
1347
|
+
hadBlurryFrames: ds.hadBlurryFrames,
|
|
1348
|
+
// The actual gates the measured values are judged against — lets support
|
|
1349
|
+
// read "measured 45 vs floor 45" instead of just a pass/fail flag.
|
|
1350
|
+
thresholds: {
|
|
1351
|
+
minBrightness: MIN_BRIGHTNESS_THRESHOLD,
|
|
1352
|
+
blur: 50
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
|
|
1233
1356
|
// Only proceed if image quality is acceptable
|
|
1234
1357
|
const hasAcceptableQuality = isRegionBright && isNotBlurry;
|
|
1235
1358
|
|
|
@@ -1274,6 +1397,7 @@ const IdentityDocumentCamera = ({
|
|
|
1274
1397
|
resultText,
|
|
1275
1398
|
mrzOnlyText: undefined,
|
|
1276
1399
|
// Will be set below if MRZ blocks detected
|
|
1400
|
+
textConfidence: frame.textConfidence,
|
|
1277
1401
|
blocks: textBlocks.map(block => ({
|
|
1278
1402
|
blockText: block.text || '',
|
|
1279
1403
|
blockFrame: block.blockFrame ?? {
|
|
@@ -5,7 +5,6 @@ import AppContext from "../Contexts/AppContext.js";
|
|
|
5
5
|
import { CommonActions, useNavigation, usePreventRemove } from '@react-navigation/native';
|
|
6
6
|
import { View, StyleSheet, Alert } from 'react-native';
|
|
7
7
|
import { useTranslation } from 'react-i18next';
|
|
8
|
-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
9
8
|
import i18n from "../../Translation/index.js";
|
|
10
9
|
import StyledButton from "./StyledButton.js";
|
|
11
10
|
import { analyticsService } from "../Services/AnalyticsService.js";
|
|
@@ -26,7 +25,6 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
26
25
|
const {
|
|
27
26
|
t
|
|
28
27
|
} = useTranslation();
|
|
29
|
-
const insets = useSafeAreaInsets();
|
|
30
28
|
const routes = {
|
|
31
29
|
VERIFICATION_SESSION_CHECK: 'VerificationSessionCheckScreen',
|
|
32
30
|
DYNAMIC_ROUTES: {
|
|
@@ -185,9 +183,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
185
183
|
allowSkipStep: appContext.currentWorkflowStep?.required
|
|
186
184
|
}));
|
|
187
185
|
return appContext.currentWorkflowStep && (!appContext.currentWorkflowStep?.required || canSkipStep) && /*#__PURE__*/_jsx(View, {
|
|
188
|
-
style:
|
|
189
|
-
paddingBottom: insets.bottom
|
|
190
|
-
}],
|
|
186
|
+
style: styles.container,
|
|
191
187
|
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
192
188
|
mode: "text",
|
|
193
189
|
onPress: goToNextRouteWithAlert,
|
|
@@ -196,11 +192,13 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
196
192
|
});
|
|
197
193
|
});
|
|
198
194
|
const styles = StyleSheet.create({
|
|
195
|
+
// Hug the text button: centered, minimal padding, no full-width opaque bar so
|
|
196
|
+
// it occupies the least area and never overlays sibling content. The screen's
|
|
197
|
+
// footer wrapper already applies the bottom safe-area inset.
|
|
199
198
|
container: {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
backgroundColor: 'white'
|
|
199
|
+
alignSelf: 'center',
|
|
200
|
+
paddingHorizontal: 16,
|
|
201
|
+
paddingVertical: 8
|
|
204
202
|
}
|
|
205
203
|
});
|
|
206
204
|
export default NavigationManager;
|
|
@@ -24,8 +24,11 @@ export default /*#__PURE__*/createContext({
|
|
|
24
24
|
currentWorkflowStep: undefined,
|
|
25
25
|
isDebugNavigated: false,
|
|
26
26
|
isTestVideoSession: false,
|
|
27
|
+
lastDocumentRead: undefined,
|
|
27
28
|
onCompleted: undefined,
|
|
28
29
|
onError: undefined,
|
|
30
|
+
onDocumentRead: undefined,
|
|
31
|
+
setLastDocumentRead: undefined,
|
|
29
32
|
setSessionId: undefined,
|
|
30
33
|
setBaseUrl: undefined
|
|
31
34
|
});
|
|
@@ -6,9 +6,37 @@ import { NFCManagerCardService } from "./nfcManagerCardService.js";
|
|
|
6
6
|
import { EIDService } from "./eidService.js";
|
|
7
7
|
import { DG1File } from "./lds/icao/dg1File.js";
|
|
8
8
|
import { DG2File } from "./lds/icao/dg2File.js";
|
|
9
|
+
import { DG11File } from "./lds/icao/dg11File.js";
|
|
9
10
|
import { Buffer } from 'buffer';
|
|
10
11
|
import { InputStream } from "./java/inputStream.js";
|
|
11
12
|
import { debugLog } from "../Libs/debug.utils.js";
|
|
13
|
+
import { diagnostics } from "../Libs/diagnostics.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract an APDU status word (hex, e.g. "6982") from an error message if the
|
|
17
|
+
* protocol layer included one. Returns undefined when none is present. Kept
|
|
18
|
+
* deliberately loose — the protocol senders embed "SW=...." / "(6982)" forms.
|
|
19
|
+
*/
|
|
20
|
+
const extractStatusWord = msg => {
|
|
21
|
+
const m = msg.match(/\b(?:SW[:= ]?)?([0-9a-fA-F]{4})\b/);
|
|
22
|
+
return m ? m[1].toUpperCase() : undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Categorise an NFC error message into a coarse, non-PII bucket. */
|
|
26
|
+
const categorizeNfcError = msg => {
|
|
27
|
+
const m = msg.toLowerCase();
|
|
28
|
+
if (m.includes('cancel')) return 'user_cancelled';
|
|
29
|
+
if (m.includes('lost') || m.includes('steady')) return 'tag_lost';
|
|
30
|
+
if (m.includes('timeout')) return 'timeout';
|
|
31
|
+
if (m.includes('not supported') || m.includes('unsupported')) {
|
|
32
|
+
return 'unsupported';
|
|
33
|
+
}
|
|
34
|
+
if (m.includes('not enabled') || m.includes('disabled')) return 'not_enabled';
|
|
35
|
+
if (m.includes('auth') || m.includes('mutual') || m.includes('security')) {
|
|
36
|
+
return 'auth_failed';
|
|
37
|
+
}
|
|
38
|
+
return 'reading_error';
|
|
39
|
+
};
|
|
12
40
|
|
|
13
41
|
// --- Minimal PNG encoder (pure JS, no native deps) ---
|
|
14
42
|
// Uses deflate stored (uncompressed) blocks for O(n) encoding speed.
|
|
@@ -275,8 +303,40 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
275
303
|
let progress = 0;
|
|
276
304
|
const nfcManagerCardService = new NFCManagerCardService();
|
|
277
305
|
const passportService = new EIDService(nfcManagerCardService, EIDService.NORMAL_MAX_TRANSCEIVE_LENGTH, EIDService.DEFAULT_MAX_BLOCKSIZE, false, true);
|
|
306
|
+
|
|
307
|
+
// Structured NFC diagnostics: record each step's outcome + timing + APDU SW
|
|
308
|
+
// into the session collector so the result screen can surface device-specific
|
|
309
|
+
// read failures. Best-effort and side-effect-only — never alters the flow.
|
|
310
|
+
diagnostics.nfcAttemptStarted();
|
|
311
|
+
const stepTimer = () => {
|
|
312
|
+
const t0 = Date.now();
|
|
313
|
+
return (step, ok, error) => {
|
|
314
|
+
const msg = error instanceof Error ? error.message : error ? String(error) : undefined;
|
|
315
|
+
// Store only the APDU status word and a COARSE category — never the raw
|
|
316
|
+
// error text, which could in theory embed field/PII values. The SW + step
|
|
317
|
+
// name + category carry all the actionable signal.
|
|
318
|
+
diagnostics.nfcStep({
|
|
319
|
+
step,
|
|
320
|
+
ok,
|
|
321
|
+
ms: Date.now() - t0,
|
|
322
|
+
sw: msg ? extractStatusWord(msg) : undefined,
|
|
323
|
+
error: msg ? categorizeNfcError(msg) : undefined
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
};
|
|
278
327
|
try {
|
|
279
|
-
|
|
328
|
+
{
|
|
329
|
+
const done = stepTimer();
|
|
330
|
+
try {
|
|
331
|
+
await passportService.open();
|
|
332
|
+
// Reaching open() without throwing means the chip powered on / was seen.
|
|
333
|
+
diagnostics.nfcChipDetected();
|
|
334
|
+
done('open', true);
|
|
335
|
+
} catch (e) {
|
|
336
|
+
done('open', false, e);
|
|
337
|
+
throw e;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
280
340
|
progress = 1;
|
|
281
341
|
if (progressCallback) {
|
|
282
342
|
progressCallback(progress);
|
|
@@ -286,12 +346,17 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
286
346
|
// Explicitly SELECT MF first so the SFI=0x1C context is correct,
|
|
287
347
|
// regardless of which application the chip activates on power-on.
|
|
288
348
|
let hasPACESucceeded = false;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
349
|
+
{
|
|
350
|
+
const done = stepTimer();
|
|
351
|
+
try {
|
|
352
|
+
await passportService.sendSelectMF();
|
|
353
|
+
done('selectMF', true);
|
|
354
|
+
debugLog('EID', '[EID] SELECT MF OK');
|
|
355
|
+
} catch (mfErr) {
|
|
356
|
+
done('selectMF', false, mfErr);
|
|
357
|
+
const mfMsg = mfErr instanceof Error ? mfErr.message : String(mfErr);
|
|
358
|
+
debugLog('EID', `[EID] SELECT MF failed (${mfMsg}), continuing with SFI read anyway`);
|
|
359
|
+
}
|
|
295
360
|
}
|
|
296
361
|
progress = 2;
|
|
297
362
|
if (progressCallback) {
|
|
@@ -301,19 +366,24 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
301
366
|
// Read EF.CardAccess — kept in a separate try/catch so we can distinguish
|
|
302
367
|
// "file read failure" from "PACE protocol failure" in the logs.
|
|
303
368
|
let cardAccessBuf = null;
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
buf
|
|
369
|
+
{
|
|
370
|
+
const done = stepTimer();
|
|
371
|
+
try {
|
|
372
|
+
const cardAccessStream = passportService.getInputStream(EIDService.EF_CARD_ACCESS);
|
|
373
|
+
await cardAccessStream.init();
|
|
374
|
+
const cardAccessLen = cardAccessStream.getLength();
|
|
375
|
+
const buf = Buffer.alloc(cardAccessLen);
|
|
376
|
+
for (let i = 0; i < cardAccessLen; i++) {
|
|
377
|
+
buf[i] = await cardAccessStream.read();
|
|
378
|
+
}
|
|
379
|
+
cardAccessBuf = buf;
|
|
380
|
+
done('readCardAccess', true);
|
|
381
|
+
debugLog('EID', `[EID] EF.CardAccess read OK: ${cardAccessLen} bytes = ${cardAccessBuf.toString('hex')}`);
|
|
382
|
+
} catch (readErr) {
|
|
383
|
+
done('readCardAccess', false, readErr);
|
|
384
|
+
const readMsg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
385
|
+
debugLog('EID', `[EID] EF.CardAccess read FAILED: ${readMsg} — falling back to BAC`);
|
|
311
386
|
}
|
|
312
|
-
cardAccessBuf = buf;
|
|
313
|
-
debugLog('EID', `[EID] EF.CardAccess read OK: ${cardAccessLen} bytes = ${cardAccessBuf.toString('hex')}`);
|
|
314
|
-
} catch (readErr) {
|
|
315
|
-
const readMsg = readErr instanceof Error ? readErr.message : String(readErr);
|
|
316
|
-
debugLog('EID', `[EID] EF.CardAccess read FAILED: ${readMsg} — falling back to BAC`);
|
|
317
387
|
}
|
|
318
388
|
progress = 3;
|
|
319
389
|
if (progressCallback) {
|
|
@@ -321,11 +391,17 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
321
391
|
}
|
|
322
392
|
|
|
323
393
|
// If EF.CardAccess was read successfully, attempt PACE.
|
|
394
|
+
if (cardAccessBuf === null) {
|
|
395
|
+
// EF.CardAccess unreadable (the readCardAccess step already recorded the
|
|
396
|
+
// failure/SW) → PACE can't be attempted; BAC will be used.
|
|
397
|
+
diagnostics.nfcPaceFellBackToBac('no_card_access');
|
|
398
|
+
}
|
|
324
399
|
if (cardAccessBuf !== null) {
|
|
325
400
|
try {
|
|
326
401
|
const paceInfo = parsePACEInfoFromCardAccess(cardAccessBuf);
|
|
327
402
|
if (paceInfo) {
|
|
328
403
|
debugLog('EID', `[EID] PACE available: oid=${paceInfo.oid} parameterId=${paceInfo.parameterId}`);
|
|
404
|
+
diagnostics.nfcSetAuth('PACE', paceInfo.oid);
|
|
329
405
|
// PACE is available - derive key from MRZ and perform PACE
|
|
330
406
|
const bacKey = new BACKey(documentNumber, dateOfBirth, dateOfExpiry);
|
|
331
407
|
const paceKey = PACEKeySpec.createMRZKey(bacKey.getKeySeedForPACE());
|
|
@@ -333,14 +409,23 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
333
409
|
if (progressCallback) {
|
|
334
410
|
progressCallback(progress);
|
|
335
411
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
412
|
+
const done = stepTimer();
|
|
413
|
+
try {
|
|
414
|
+
await passportService.doPACE(paceKey, paceInfo.oid, paceInfo.parameterId, progressCallback);
|
|
415
|
+
hasPACESucceeded = true;
|
|
416
|
+
done('paceAuth', true);
|
|
417
|
+
debugLog('EID', '[EID] PACE succeeded');
|
|
418
|
+
} catch (paceErr) {
|
|
419
|
+
done('paceAuth', false, paceErr);
|
|
420
|
+
throw paceErr;
|
|
421
|
+
}
|
|
339
422
|
} else {
|
|
423
|
+
diagnostics.nfcPaceFellBackToBac('no_pace_info');
|
|
340
424
|
debugLog('EID', '[EID] EF.CardAccess read OK but no PACE SecurityInfo found — document does not support PACE, falling back to BAC');
|
|
341
425
|
}
|
|
342
426
|
} catch (paceError) {
|
|
343
427
|
// PACE protocol exchange failed — fall back to BAC
|
|
428
|
+
diagnostics.nfcPaceFellBackToBac('pace_failed');
|
|
344
429
|
const msg = paceError instanceof Error ? paceError.message : String(paceError);
|
|
345
430
|
debugLog('EID', `[EID] PACE protocol failed (${msg}), falling back to BAC`);
|
|
346
431
|
if (paceError instanceof Error && paceError.stack) {
|
|
@@ -354,7 +439,16 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
354
439
|
}
|
|
355
440
|
|
|
356
441
|
// Select Applet for MRTD
|
|
357
|
-
|
|
442
|
+
{
|
|
443
|
+
const done = stepTimer();
|
|
444
|
+
try {
|
|
445
|
+
await passportService.sendSelectApplet(hasPACESucceeded);
|
|
446
|
+
done('selectApplet', true);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
done('selectApplet', false, e);
|
|
449
|
+
throw e;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
358
452
|
progress = 10;
|
|
359
453
|
if (progressCallback) {
|
|
360
454
|
progressCallback(progress);
|
|
@@ -367,8 +461,16 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
367
461
|
await efComStream.read();
|
|
368
462
|
} catch (error) {
|
|
369
463
|
// EF_COM not available -> try to do BAC
|
|
464
|
+
diagnostics.nfcSetAuth('BAC');
|
|
370
465
|
const bacKey = new BACKey(documentNumber, dateOfBirth, dateOfExpiry);
|
|
371
|
-
|
|
466
|
+
const done = stepTimer();
|
|
467
|
+
try {
|
|
468
|
+
await passportService.doBAC(bacKey);
|
|
469
|
+
done('bacAuth', true);
|
|
470
|
+
} catch (e) {
|
|
471
|
+
done('bacAuth', false, e);
|
|
472
|
+
throw e;
|
|
473
|
+
}
|
|
372
474
|
}
|
|
373
475
|
}
|
|
374
476
|
progress = 20;
|
|
@@ -377,16 +479,39 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
377
479
|
}
|
|
378
480
|
|
|
379
481
|
// Read DG1 (MRZ Info)
|
|
380
|
-
const
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
482
|
+
const dg1Done = stepTimer();
|
|
483
|
+
let mrzInfo;
|
|
484
|
+
try {
|
|
485
|
+
const dg1InputStream = passportService.getInputStream(EIDService.EF_DG1);
|
|
486
|
+
await dg1InputStream.init();
|
|
487
|
+
const dg1File = new DG1File(undefined, dg1InputStream);
|
|
488
|
+
mrzInfo = await dg1File.getMRZInfo();
|
|
489
|
+
dg1Done('readDG1', true);
|
|
490
|
+
} catch (e) {
|
|
491
|
+
dg1Done('readDG1', false, e);
|
|
492
|
+
throw e;
|
|
493
|
+
}
|
|
384
494
|
progress = 30;
|
|
385
495
|
if (progressCallback) {
|
|
386
496
|
progressCallback(progress);
|
|
387
497
|
}
|
|
388
498
|
|
|
499
|
+
// Read DG11 (Additional Personal Data — printed name in UTF-8, optional)
|
|
500
|
+
let dg11FirstName = null;
|
|
501
|
+
let dg11LastName = null;
|
|
502
|
+
try {
|
|
503
|
+
const dg11InputStream = passportService.getInputStream(EIDService.EF_DG11);
|
|
504
|
+
await dg11InputStream.init();
|
|
505
|
+
const dg11File = new DG11File(dg11InputStream);
|
|
506
|
+
await dg11File.read();
|
|
507
|
+
dg11FirstName = dg11File.getFirstName();
|
|
508
|
+
dg11LastName = dg11File.getLastName();
|
|
509
|
+
} catch {
|
|
510
|
+
// DG11 is optional — not all chips have it, and access may be denied
|
|
511
|
+
}
|
|
512
|
+
|
|
389
513
|
// Read DG2 (Face Image)
|
|
514
|
+
const dg2Done = stepTimer();
|
|
390
515
|
const dg2InputStream = passportService.getInputStream(EIDService.EF_DG2);
|
|
391
516
|
await dg2InputStream.init();
|
|
392
517
|
InputStream.onRead((totalBytesRead, fileLength) => {
|
|
@@ -399,7 +524,14 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
399
524
|
let imageAsBase64 = '';
|
|
400
525
|
let mimeType = '';
|
|
401
526
|
const allFaceImageInfos = [];
|
|
402
|
-
|
|
527
|
+
let faceInfos;
|
|
528
|
+
try {
|
|
529
|
+
faceInfos = await dg2File.getFaceInfos();
|
|
530
|
+
dg2Done('readDG2', true);
|
|
531
|
+
} catch (e) {
|
|
532
|
+
dg2Done('readDG2', false, e);
|
|
533
|
+
throw e;
|
|
534
|
+
}
|
|
403
535
|
for (let faceInfo of faceInfos) {
|
|
404
536
|
allFaceImageInfos.push(...faceInfo.getFaceImageInfos());
|
|
405
537
|
}
|
|
@@ -418,12 +550,17 @@ const eidReader = async (documentNumber, dateOfBirth, dateOfExpiry, progressCall
|
|
|
418
550
|
if (progressCallback) {
|
|
419
551
|
progressCallback(progress);
|
|
420
552
|
}
|
|
553
|
+
diagnostics.nfcResult(true);
|
|
421
554
|
return {
|
|
422
555
|
mrz: mrzInfo,
|
|
423
556
|
faceImage: imageAsBase64,
|
|
424
|
-
mimeType: mimeType
|
|
557
|
+
mimeType: mimeType,
|
|
558
|
+
dg11FirstName,
|
|
559
|
+
dg11LastName
|
|
425
560
|
};
|
|
426
561
|
} catch (error) {
|
|
562
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
563
|
+
diagnostics.nfcResult(false, categorizeNfcError(msg));
|
|
427
564
|
debugLog('EID', 'Error reading passport data', error);
|
|
428
565
|
} finally {
|
|
429
566
|
await passportService.close();
|