@trustchex/react-native-sdk 1.513.1 → 1.513.3
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/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +9 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +56 -153
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +1 -9
- package/lib/module/Translation/Resources/en.js +0 -2
- package/lib/module/Translation/Resources/tr.js +0 -2
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +0 -2
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -2
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +9 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +62 -196
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +0 -9
- package/src/Translation/Resources/en.ts +0 -4
- package/src/Translation/Resources/tr.ts +0 -4
- package/src/version.ts +1 -1
|
@@ -15,6 +15,15 @@ export const MIN_MLI_FACE_SIZE_PERCENT = 0.01; // MLI (Multi Layer Image): min 1
|
|
|
15
15
|
export const REQUIRED_CONSISTENT_MRZ_READS = 2;
|
|
16
16
|
export const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
|
|
17
17
|
|
|
18
|
+
// Frames a check-digit-VALID MRZ consensus must stay UNCHANGED before it is
|
|
19
|
+
// accepted regardless of the per-cell vote-margin ("confident") gate. This
|
|
20
|
+
// unblocks a dense passport MRZ where one stubborn OCR-B look-alike keeps a
|
|
21
|
+
// single cell below the margin threshold forever — the scan would otherwise
|
|
22
|
+
// wedge on "hold steady". Set well above the aggregator's stabilityTarget (3)
|
|
23
|
+
// so the normal confident+stable path handles the common case and this only
|
|
24
|
+
// rescues the persistent-look-alike tail (~1.5s at ~8fps).
|
|
25
|
+
export const MRZ_STREAK_ACCEPT_OVERRIDE = 12;
|
|
26
|
+
|
|
18
27
|
// After this long on a single scan step without completing it, surface the
|
|
19
28
|
// "Having trouble?" prompt so the user isn't stranded on a live camera with no
|
|
20
29
|
// timeout, retry, or help. Per-step (re-armed on each step transition).
|
|
@@ -24,7 +24,7 @@ import { useTheme } from "../Contexts/ThemeContext.js";
|
|
|
24
24
|
import DebugOverlay, { TestModePanel } from "./DebugOverlay.js";
|
|
25
25
|
import { getStatusMessage, getFrameToScreenTransform, transformBoundsToScreen, getScanAreaBounds, angleBetweenPoints, detectDocumentType, determineDocumentTypeToSet, areMRZFieldsEqual, hasRequiredMRZFields, validateFacePosition, isPassportDocumentCode, StableFlag } from "./IdentityDocumentCamera.utils.js";
|
|
26
26
|
import { handlePassportFlow, handleIDFrontFlow, handleIDBackFlow, getNextStepAfterHologram } from "./IdentityDocumentCamera.flows.js";
|
|
27
|
-
import { HOLOGRAM_IMAGE_COUNT, HOLOGRAM_DETECTION_THRESHOLD, HOLOGRAM_DETECTION_RETRY_COUNT, HOLOGRAM_CAPTURE_INTERVAL, HOLOGRAM_MAX_FRAMES_WITHOUT_FACE, MIN_BRIGHTNESS_THRESHOLD,
|
|
27
|
+
import { HOLOGRAM_IMAGE_COUNT, HOLOGRAM_DETECTION_THRESHOLD, HOLOGRAM_DETECTION_RETRY_COUNT, HOLOGRAM_CAPTURE_INTERVAL, HOLOGRAM_MAX_FRAMES_WITHOUT_FACE, MIN_BRIGHTNESS_THRESHOLD, FACE_EDGE_MARGIN_PERCENT, MAX_CONSECUTIVE_QUALITY_FAILURES, REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS, MRZ_STREAK_ACCEPT_OVERRIDE, HELP_PROMPT_DELAY_MS, SIGNATURE_TEXT_REGEX, MRZ_BLOCK_PATTERN, MIN_CARD_FACE_SIZE_PERCENT } from "./IdentityDocumentCamera.constants.js";
|
|
28
28
|
|
|
29
29
|
// Re-export types for backward compatibility
|
|
30
30
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
@@ -72,11 +72,6 @@ const IdentityDocumentCamera = ({
|
|
|
72
72
|
const [currentSecondaryFaceImage, setCurrentSecondaryFaceImage] = useState(undefined);
|
|
73
73
|
const [isBrightnessLow, setIsBrightnessLow] = useState(false);
|
|
74
74
|
const [isFrameBlurry, setIsFrameBlurry] = useState(false);
|
|
75
|
-
// Glare was being silently rejected (frames dropped with no user feedback), so
|
|
76
|
-
// a user fighting reflections just saw the scan hang. Surface it so we can show
|
|
77
|
-
// an actionable "move away from glare" hint. Set at each glare rejection;
|
|
78
|
-
// cleared when a frame passes the brightness check (a fresh, glare-free frame).
|
|
79
|
-
const [isGlareDetected, setIsGlareDetected] = useState(false);
|
|
80
75
|
const [hasGuideShown, setHasGuideShown] = useState(false);
|
|
81
76
|
// "Having trouble?" escape: if a scan step doesn't complete within
|
|
82
77
|
// HELP_PROMPT_DELAY_MS the user is otherwise stuck on a live camera forever
|
|
@@ -99,7 +94,7 @@ const IdentityDocumentCamera = ({
|
|
|
99
94
|
const consecutiveQualityFailures = useRef(0);
|
|
100
95
|
const mrzDetectionCurrentRetryCount = useRef(0);
|
|
101
96
|
|
|
102
|
-
// Hysteresis for the on-screen quality guidance. Brightness/blur
|
|
97
|
+
// Hysteresis for the on-screen quality guidance. Brightness/blur are
|
|
103
98
|
// measured every frame; feeding the raw per-frame booleans straight into
|
|
104
99
|
// state made the guidance strobe (a single blurry hand-shake frame flipped
|
|
105
100
|
// "hold steady" on and off). These smooth each flag: it appears after a few
|
|
@@ -108,19 +103,11 @@ const IdentityDocumentCamera = ({
|
|
|
108
103
|
// problems), clear slow (don't flicker off on one good frame).
|
|
109
104
|
const brightnessLowFlag = useRef(new StableFlag(2, 5));
|
|
110
105
|
const blurryFlag = useRef(new StableFlag(3, 6));
|
|
111
|
-
const glareFlag = useRef(new StableFlag(2, 5));
|
|
112
|
-
// Glare is asserted by accept-time helpers that run inside the (separately
|
|
113
|
-
// scoped) handleFaceAndText callback, so it can't flow through a handler
|
|
114
|
-
// local. This ref is cleared at the top of each frame and set true if any
|
|
115
|
-
// glare check in that frame found glare; the handler reads it to feed
|
|
116
|
-
// `glareFlag`. `hadGlareText` distinguishes "card present but glaring" from
|
|
117
|
-
// an empty view so the warning only shows over an actual document.
|
|
118
|
-
const rawGlareRef = useRef(false);
|
|
119
106
|
// Whether a document was visible in the PREVIOUS frame (recognized text,
|
|
120
107
|
// barcode, or face). The quality guidance is stabilized BEFORE the quality
|
|
121
108
|
// gate (so dark/blurry frames — which the gate early-returns on — can still
|
|
122
109
|
// raise their warnings), but document-presence is only known LATER in the
|
|
123
|
-
// frame, so blur
|
|
110
|
+
// frame, so blur is gated on the previous frame's value. One-frame lag
|
|
124
111
|
// at ~8fps is imperceptible and the hysteresis smooths it.
|
|
125
112
|
const documentPresentRef = useRef(false);
|
|
126
113
|
|
|
@@ -191,8 +178,7 @@ const IdentityDocumentCamera = ({
|
|
|
191
178
|
frameW: 0,
|
|
192
179
|
frameH: 0,
|
|
193
180
|
hadLowBrightness: false,
|
|
194
|
-
hadBlurryFrames: false
|
|
195
|
-
hadGlare: false
|
|
181
|
+
hadBlurryFrames: false
|
|
196
182
|
});
|
|
197
183
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
198
184
|
const faceImages = useRef([]);
|
|
@@ -258,8 +244,7 @@ const IdentityDocumentCamera = ({
|
|
|
258
244
|
frameW: 0,
|
|
259
245
|
frameH: 0,
|
|
260
246
|
hadLowBrightness: false,
|
|
261
|
-
hadBlurryFrames: false
|
|
262
|
-
hadGlare: false
|
|
247
|
+
hadBlurryFrames: false
|
|
263
248
|
};
|
|
264
249
|
cachedBarcode.current = null;
|
|
265
250
|
isCompletionCallbackInvoked.current = false;
|
|
@@ -295,7 +280,7 @@ const IdentityDocumentCamera = ({
|
|
|
295
280
|
}, [isFocused, hasPermission, hasGuideShown]);
|
|
296
281
|
useEffect(() => {
|
|
297
282
|
if (!hasGuideShown || !appContext.currentWorkflowStep?.data?.voiceGuidanceActive) return;
|
|
298
|
-
const message = getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry,
|
|
283
|
+
const message = getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t);
|
|
299
284
|
if (message) {
|
|
300
285
|
if (nextStep === 'COMPLETED') {
|
|
301
286
|
if (isCompletionAnnouncementSpoken.current) {
|
|
@@ -308,7 +293,7 @@ const IdentityDocumentCamera = ({
|
|
|
308
293
|
const shouldBypassInterval = nextStep !== 'COMPLETED';
|
|
309
294
|
speak(message, shouldBypassInterval);
|
|
310
295
|
}
|
|
311
|
-
}, [appContext.currentWorkflowStep?.data?.voiceGuidanceActive, hasGuideShown, isBrightnessLow, isFrameBlurry,
|
|
296
|
+
}, [appContext.currentWorkflowStep?.data?.voiceGuidanceActive, hasGuideShown, isBrightnessLow, isFrameBlurry, nextStep, status, detectedDocumentType, allElementsDetected, elementsOutsideScanArea, t]);
|
|
312
297
|
useEffect(() => {
|
|
313
298
|
if (status === 'INCORRECT') {
|
|
314
299
|
const timeout = setTimeout(() => {
|
|
@@ -436,54 +421,12 @@ const IdentityDocumentCamera = ({
|
|
|
436
421
|
}
|
|
437
422
|
};
|
|
438
423
|
|
|
439
|
-
// Check if face image has glare (brightness exceeds threshold). Also surfaces
|
|
440
|
-
// the result to `isGlareDetected` so the user gets an actionable hint instead
|
|
441
|
-
// of a silently-stalled scan.
|
|
442
|
-
const checkFaceGlare = async faceImage => {
|
|
443
|
-
try {
|
|
444
|
-
// Check entire face region for glare
|
|
445
|
-
const hasGlare = await OpenCVModule.isRectangularRegionBright(faceImage, 0, 0, 100,
|
|
446
|
-
// Full face width
|
|
447
|
-
100,
|
|
448
|
-
// Full face height
|
|
449
|
-
MAX_BRIGHTNESS_THRESHOLD);
|
|
450
|
-
// OR-accumulate into the per-frame glare signal (reset at frame top); the
|
|
451
|
-
// frame handler feeds it through the stabilized UI flag. Also record the
|
|
452
|
-
// diagnostic.
|
|
453
|
-
if (hasGlare) {
|
|
454
|
-
rawGlareRef.current = true;
|
|
455
|
-
diagStats.current.hadGlare = true;
|
|
456
|
-
}
|
|
457
|
-
return hasGlare;
|
|
458
|
-
} catch (error) {
|
|
459
|
-
return false; // Assume no glare on error
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
|
|
463
424
|
// Check if face is fully visible (not cropped at edges)
|
|
464
425
|
const isFaceFullyVisible = (face, frameWidth, frameHeight) => {
|
|
465
426
|
const margin = FACE_EDGE_MARGIN_PERCENT;
|
|
466
427
|
const bounds = face.bounds;
|
|
467
428
|
return bounds.x >= frameWidth * margin && bounds.y >= frameHeight * margin && bounds.x + bounds.width <= frameWidth * (1 - margin) && bounds.y + bounds.height <= frameHeight * (1 - margin);
|
|
468
429
|
};
|
|
469
|
-
|
|
470
|
-
// Check if document image has glare
|
|
471
|
-
const checkDocumentGlare = async (image, width, height) => {
|
|
472
|
-
try {
|
|
473
|
-
// Check center 80% region for glare (document area)
|
|
474
|
-
const hasGlare = await OpenCVModule.isRectangularRegionBright(image, Math.round(width * 0.1), Math.round(height * 0.1), Math.round(width * 0.8), Math.round(height * 0.8), MAX_BRIGHTNESS_THRESHOLD);
|
|
475
|
-
// OR-accumulate into the per-frame glare signal (reset at frame top); the
|
|
476
|
-
// frame handler feeds it through the stabilized UI flag. Also record the
|
|
477
|
-
// diagnostic.
|
|
478
|
-
if (hasGlare) {
|
|
479
|
-
rawGlareRef.current = true;
|
|
480
|
-
diagStats.current.hadGlare = true;
|
|
481
|
-
}
|
|
482
|
-
return hasGlare;
|
|
483
|
-
} catch (error) {
|
|
484
|
-
return false;
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
430
|
const setNextStepAndVibrate = useCallback((nextStepType, fromStep) => {
|
|
488
431
|
if (fromStep) {
|
|
489
432
|
setCompletedStep(fromStep);
|
|
@@ -550,12 +493,9 @@ const IdentityDocumentCamera = ({
|
|
|
550
493
|
// carried over from the previous side).
|
|
551
494
|
brightnessLowFlag.current.reset(false);
|
|
552
495
|
blurryFlag.current.reset(false);
|
|
553
|
-
glareFlag.current.reset(false);
|
|
554
|
-
rawGlareRef.current = false;
|
|
555
496
|
documentPresentRef.current = false;
|
|
556
497
|
setIsBrightnessLow(false);
|
|
557
498
|
setIsFrameBlurry(false);
|
|
558
|
-
setIsGlareDetected(false);
|
|
559
499
|
if (nextStepType !== 'COMPLETED') {
|
|
560
500
|
setTimeout(() => {
|
|
561
501
|
setStatus('SEARCHING');
|
|
@@ -818,13 +758,11 @@ const IdentityDocumentCamera = ({
|
|
|
818
758
|
if (shouldCropFaces && croppedFaces.length > 0 && croppedFaces[0] && facePositionValid && !currentFaceImage) {
|
|
819
759
|
// Validate face quality before accepting
|
|
820
760
|
const faceFullyVisible = primaryFaces[0] && isFaceFullyVisible(primaryFaces[0], frameWidth, frameHeight);
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
// Reject face with glare or partially visible
|
|
761
|
+
if (!faceFullyVisible) {
|
|
762
|
+
// Reject partially visible face
|
|
824
763
|
if (isDebugEnabled()) {
|
|
825
764
|
debugLog('IdentityDocumentCamera', '[FACE] Rejected', {
|
|
826
|
-
fullyVisible: faceFullyVisible
|
|
827
|
-
hasGlare
|
|
765
|
+
fullyVisible: faceFullyVisible
|
|
828
766
|
});
|
|
829
767
|
}
|
|
830
768
|
// Continue scanning without locking this face
|
|
@@ -872,7 +810,19 @@ const IdentityDocumentCamera = ({
|
|
|
872
810
|
// vote must be unchanged across `stabilityTarget` consecutive contributing
|
|
873
811
|
// frames, which is exactly the "only accept once stable" guarantee.
|
|
874
812
|
// `addFrame` runs for every frame above, so the consensus is always current.
|
|
875
|
-
|
|
813
|
+
// `consensus.stable` also requires a "confident" vote — every contested
|
|
814
|
+
// cell's winner must lead by a margin. On a dense TD3 passport MRZ (88
|
|
815
|
+
// cells) a single stubborn OCR-B look-alike (A↔4, S↔5) can keep one cell
|
|
816
|
+
// at a 1-vote margin indefinitely, so `stable` never flips and the scan
|
|
817
|
+
// wedges on "hold steady" — even though the check-digit-VALID consensus
|
|
818
|
+
// has been UNCHANGED for many frames. Check digits are the real correctness
|
|
819
|
+
// guarantee; a long unchanged, valid streak is conclusive. So also accept
|
|
820
|
+
// once a valid consensus has held unchanged for MRZ_STREAK_ACCEPT_OVERRIDE
|
|
821
|
+
// frames (well above `stabilityTarget`), independent of the margin gate.
|
|
822
|
+
// Short streaks still require the full confident+stable gate, preserving
|
|
823
|
+
// the early-misread protection.
|
|
824
|
+
const mrzValidStreakSettled = parsedMRZData?.valid === true && consensus.stableStreak >= MRZ_STREAK_ACCEPT_OVERRIDE;
|
|
825
|
+
const mrzStableAndValid = (consensus.stable || mrzValidStreakSettled) && parsedMRZData?.valid === true;
|
|
876
826
|
|
|
877
827
|
// Surface the reliable-detection signal to the render so the UI can show a
|
|
878
828
|
// green "GO" the moment (and only once) the consensus is stable + valid.
|
|
@@ -940,16 +890,6 @@ const IdentityDocumentCamera = ({
|
|
|
940
890
|
setStatus(mrzText ? 'SCANNING' : 'SEARCHING');
|
|
941
891
|
return;
|
|
942
892
|
}
|
|
943
|
-
|
|
944
|
-
// Check for glare on ID back before accepting
|
|
945
|
-
const hasGlare = await checkDocumentGlare(image, frameWidth, frameHeight);
|
|
946
|
-
if (hasGlare) {
|
|
947
|
-
if (isDebugEnabled()) {
|
|
948
|
-
debugLog('IdentityDocumentCamera', '[ID_BACK] Rejected - glare detected');
|
|
949
|
-
}
|
|
950
|
-
setStatus('SCANNING');
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
893
|
if (isDebugEnabled()) {
|
|
954
894
|
debugLog('IdentityDocumentCamera', '[ID_BACK] MRZ validated', {
|
|
955
895
|
documentNumber: parsedMRZData?.fields?.documentNumber,
|
|
@@ -1071,22 +1011,13 @@ const IdentityDocumentCamera = ({
|
|
|
1071
1011
|
if (!isTorchOnRef.current) {
|
|
1072
1012
|
setIsTorchOn(true);
|
|
1073
1013
|
}
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
if (
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
if (isDebugEnabled()) {
|
|
1082
|
-
debugLog('IdentityDocumentCamera', '[HOLOGRAM] Frame captured', {
|
|
1083
|
-
frameIndex: faceImages.current.length - 1,
|
|
1084
|
-
totalFrames: faceImages.current.length
|
|
1085
|
-
});
|
|
1086
|
-
}
|
|
1087
|
-
} else if (isDebugEnabled()) {
|
|
1088
|
-
debugLog('IdentityDocumentCamera', '[HOLOGRAM] Rejected glare frame', {
|
|
1089
|
-
collectedCount: faceImages.current.length
|
|
1014
|
+
faceImages.current.push(primaryFaceOnly);
|
|
1015
|
+
lastHologramCaptureTime.current = Date.now();
|
|
1016
|
+
hologramImageCountRef.current = faceImages.current.length;
|
|
1017
|
+
if (isDebugEnabled()) {
|
|
1018
|
+
debugLog('IdentityDocumentCamera', '[HOLOGRAM] Frame captured', {
|
|
1019
|
+
frameIndex: faceImages.current.length - 1,
|
|
1020
|
+
totalFrames: faceImages.current.length
|
|
1090
1021
|
});
|
|
1091
1022
|
}
|
|
1092
1023
|
if (faceImages.current.length > 0) {
|
|
@@ -1356,16 +1287,6 @@ const IdentityDocumentCamera = ({
|
|
|
1356
1287
|
continueScanning(true);
|
|
1357
1288
|
return;
|
|
1358
1289
|
}
|
|
1359
|
-
|
|
1360
|
-
// Check for glare on passport before accepting
|
|
1361
|
-
const hasGlare = await checkDocumentGlare(image, frameWidth, frameHeight);
|
|
1362
|
-
if (hasGlare) {
|
|
1363
|
-
if (isDebugEnabled()) {
|
|
1364
|
-
debugLog('IdentityDocumentCamera', '[PASSPORT] Rejected - glare detected');
|
|
1365
|
-
}
|
|
1366
|
-
continueScanning(false);
|
|
1367
|
-
return;
|
|
1368
|
-
}
|
|
1369
1290
|
setDetectedDocumentType('PASSPORT');
|
|
1370
1291
|
setStatus('SCANNED');
|
|
1371
1292
|
const nextPassportStep = flowResult.nextAction === 'PROCEED_TO_COMPLETED' ? 'COMPLETED' : 'SCAN_HOLOGRAM';
|
|
@@ -1382,16 +1303,6 @@ const IdentityDocumentCamera = ({
|
|
|
1382
1303
|
continueScanning(flowResult.nextAction !== 'REJECT_AS_PASSPORT');
|
|
1383
1304
|
return;
|
|
1384
1305
|
}
|
|
1385
|
-
|
|
1386
|
-
// Check for glare on ID front before accepting
|
|
1387
|
-
const hasGlare = await checkDocumentGlare(image, frameWidth, frameHeight);
|
|
1388
|
-
if (hasGlare) {
|
|
1389
|
-
if (isDebugEnabled()) {
|
|
1390
|
-
debugLog('IdentityDocumentCamera', '[ID_FRONT] Rejected - glare detected');
|
|
1391
|
-
}
|
|
1392
|
-
continueScanning(false);
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
1306
|
setDetectedDocumentType('ID_FRONT');
|
|
1396
1307
|
setStatus('SCANNED');
|
|
1397
1308
|
const nextIdFrontStep = onlyMRZScan ? 'SCAN_ID_BACK' : 'SCAN_HOLOGRAM';
|
|
@@ -1460,18 +1371,6 @@ const IdentityDocumentCamera = ({
|
|
|
1460
1371
|
const rawBrightnessLow = !isRegionBright;
|
|
1461
1372
|
if (rawBrightnessLow) diagStats.current.hadLowBrightness = true;
|
|
1462
1373
|
|
|
1463
|
-
// Per-frame glare is asserted by the accept-time helpers (checkDocumentGlare
|
|
1464
|
-
// /checkFaceGlare), but those run inside handleFaceAndText which is fired
|
|
1465
|
-
// (async, un-awaited) LATER in this handler — so this frame's glare result
|
|
1466
|
-
// isn't known yet at the stabilization block below. We therefore feed the
|
|
1467
|
-
// stabilizer the PREVIOUS frame's glare signal (captured here before the
|
|
1468
|
-
// reset). At ~8fps the one-frame lag is imperceptible and the hysteresis
|
|
1469
|
-
// smooths it anyway; without this the read would always see false and the
|
|
1470
|
-
// glare warning could never fire. Reset after capturing so a now-clean
|
|
1471
|
-
// view clears (no latch).
|
|
1472
|
-
const prevFrameGlare = rawGlareRef.current;
|
|
1473
|
-
rawGlareRef.current = false;
|
|
1474
|
-
|
|
1475
1374
|
// Check blur only in center region (area of interest) to avoid false positives
|
|
1476
1375
|
// from iOS depth-of-field background blur
|
|
1477
1376
|
let isNotBlurry = true;
|
|
@@ -1521,8 +1420,8 @@ const IdentityDocumentCamera = ({
|
|
|
1521
1420
|
// MUST run BEFORE the quality-gate early-return below: a dark or blurry
|
|
1522
1421
|
// frame is exactly what makes hasAcceptableQuality false, so if this ran
|
|
1523
1422
|
// after the gate the "too dark" / "hold steady" warnings could never
|
|
1524
|
-
// raise (their triggering frames would have already returned). Blur
|
|
1525
|
-
//
|
|
1423
|
+
// raise (their triggering frames would have already returned). Blur
|
|
1424
|
+
// is gated on document presence; brightness shows regardless, since a
|
|
1526
1425
|
// dark room is worth flagging before the card is even framed.
|
|
1527
1426
|
//
|
|
1528
1427
|
// Presence combines the previous frame's confirmed value (documentPresent-
|
|
@@ -1531,13 +1430,12 @@ const IdentityDocumentCamera = ({
|
|
|
1531
1430
|
// processing). Without the raw proxy, a document that is blurry/dark from
|
|
1532
1431
|
// its very first framed moment would keep failing the quality gate and
|
|
1533
1432
|
// early-returning before documentPresentRef is ever set true — suppressing
|
|
1534
|
-
// its blur
|
|
1433
|
+
// its blur hints for ~30 frames. The proxy lets the hint fire on the
|
|
1535
1434
|
// first framed frame.
|
|
1536
1435
|
const rawDocumentPresent = (frame.faces?.length ?? 0) > 0 || (frame.textBlocks?.length ?? 0) > 0 || (frame.barcodes?.length ?? 0) > 0;
|
|
1537
1436
|
const documentWasPresent = documentPresentRef.current || rawDocumentPresent;
|
|
1538
1437
|
setIsBrightnessLow(brightnessLowFlag.current.push(rawBrightnessLow));
|
|
1539
1438
|
setIsFrameBlurry(blurryFlag.current.push(documentWasPresent && isBlurry));
|
|
1540
|
-
setIsGlareDetected(glareFlag.current.push(documentWasPresent && prevFrameGlare));
|
|
1541
1439
|
// -------------------------------------------------------------------
|
|
1542
1440
|
|
|
1543
1441
|
// Store quality metrics in ref for access in handleFaceAndText callback
|
|
@@ -1598,6 +1496,24 @@ const IdentityDocumentCamera = ({
|
|
|
1598
1496
|
}))
|
|
1599
1497
|
};
|
|
1600
1498
|
|
|
1499
|
+
// Isolate the MRZ text (bottom-of-document blocks matching the MRZ
|
|
1500
|
+
// pattern) and feed ONLY that to the multi-frame consensus. CRITICAL:
|
|
1501
|
+
// this must run for EVERY build, not just debug — the identical logic
|
|
1502
|
+
// used to live only inside an `if (isDebugEnabled())` overlay block, so
|
|
1503
|
+
// in release `mrzOnlyText` was never set and the aggregator fell back to
|
|
1504
|
+
// the FULL OCR text (including the passport header "PASSPORT / TÜR /
|
|
1505
|
+
// REPUBLIC OF…"). That header noise kept the per-character consensus from
|
|
1506
|
+
// ever stabilizing, so a passport wedged on "hold steady" forever in
|
|
1507
|
+
// release while working in debug/example. Passport MRZ sits low on the
|
|
1508
|
+
// page; the `y > bottom-half` filter drops the header/photo-area text.
|
|
1509
|
+
if (textBlocks.length > 0) {
|
|
1510
|
+
const bottomHalf = frame.height * 0.5;
|
|
1511
|
+
const mrzBlocks = textBlocks.filter(block => block.blockFrame && block.blockFrame.y > bottomHalf && MRZ_BLOCK_PATTERN.test(block.text));
|
|
1512
|
+
if (mrzBlocks.length > 0) {
|
|
1513
|
+
scannedText.mrzOnlyText = [...mrzBlocks].sort((a, b) => (a.blockFrame?.y || 0) - (b.blockFrame?.y || 0)).map(b => b.text).join('\n');
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1601
1517
|
// Read barcodes directly from native ML Kit results
|
|
1602
1518
|
let barcodes = [];
|
|
1603
1519
|
if (frame.barcodes) {
|
|
@@ -1979,12 +1895,6 @@ const IdentityDocumentCamera = ({
|
|
|
1979
1895
|
current: 3,
|
|
1980
1896
|
total: 3
|
|
1981
1897
|
})}` : ''
|
|
1982
|
-
}), nextStep === 'SCAN_HOLOGRAM' && status !== 'SCANNED' && /*#__PURE__*/_jsx(TextView, {
|
|
1983
|
-
style: styles.hologramProgress,
|
|
1984
|
-
children: t('identityDocumentCamera.hologramProgress', {
|
|
1985
|
-
current: hologramImageCount,
|
|
1986
|
-
total: HOLOGRAM_IMAGE_COUNT
|
|
1987
|
-
})
|
|
1988
1898
|
}), /*#__PURE__*/_jsx(AnimatedText, {
|
|
1989
1899
|
style: [styles.topZoneText,
|
|
1990
1900
|
// Priority order for coloring (later styles override earlier ones)
|
|
@@ -1997,12 +1907,12 @@ const IdentityDocumentCamera = ({
|
|
|
1997
1907
|
opacity: errorFlashAnim
|
|
1998
1908
|
},
|
|
1999
1909
|
// 3. Warning (yellow) - quality issues
|
|
2000
|
-
(isBrightnessLow || isFrameBlurry
|
|
1910
|
+
(isBrightnessLow || isFrameBlurry) && styles.topZoneTextWarning,
|
|
2001
1911
|
// 4. Scanning (green) - all elements detected AND inside scan area
|
|
2002
|
-
status === 'SCANNING' && allElementsDetected && elementsOutsideScanArea.length === 0 && !isBrightnessLow && !isFrameBlurry &&
|
|
1912
|
+
status === 'SCANNING' && allElementsDetected && elementsOutsideScanArea.length === 0 && !isBrightnessLow && !isFrameBlurry && styles.topZoneTextScanning
|
|
2003
1913
|
// 5. Default (white) - aligning (not all detected OR elements outside scan area)
|
|
2004
1914
|
],
|
|
2005
|
-
children: getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry,
|
|
1915
|
+
children: getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t)
|
|
2006
1916
|
})]
|
|
2007
1917
|
}), /*#__PURE__*/_jsx(View, {
|
|
2008
1918
|
style: styles.leftZone
|
|
@@ -2034,7 +1944,7 @@ const IdentityDocumentCamera = ({
|
|
|
2034
1944
|
loop: true,
|
|
2035
1945
|
autoPlay: true
|
|
2036
1946
|
}) : null
|
|
2037
|
-
}), isDebugEnabled() && /*#__PURE__*/_jsx(DebugOverlay, {
|
|
1947
|
+
}), __DEV__ && isDebugEnabled() && /*#__PURE__*/_jsx(DebugOverlay, {
|
|
2038
1948
|
nextStep: nextStep,
|
|
2039
1949
|
status: status,
|
|
2040
1950
|
detectedDocumentType: detectedDocumentType,
|
|
@@ -2054,7 +1964,7 @@ const IdentityDocumentCamera = ({
|
|
|
2054
1964
|
hologramImageCount: hologramImageCount,
|
|
2055
1965
|
allElementsDetected: allElementsDetected,
|
|
2056
1966
|
elementsOutsideScanArea: elementsOutsideScanArea
|
|
2057
|
-
}), testMode && testModeData && /*#__PURE__*/_jsx(TestModePanel, {
|
|
1967
|
+
}), __DEV__ && testMode && testModeData && /*#__PURE__*/_jsx(TestModePanel, {
|
|
2058
1968
|
mrzText: testModeData.mrzText
|
|
2059
1969
|
}), showHelpPrompt && !helpSheetVisible && nextStep !== 'COMPLETED' && /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
2060
1970
|
style: styles.helpPrompt,
|
|
@@ -2171,13 +2081,6 @@ const styles = StyleSheet.create({
|
|
|
2171
2081
|
fontWeight: '500',
|
|
2172
2082
|
marginBottom: 4
|
|
2173
2083
|
},
|
|
2174
|
-
hologramProgress: {
|
|
2175
|
-
color: '#4CAF50',
|
|
2176
|
-
fontSize: 14,
|
|
2177
|
-
textAlign: 'center',
|
|
2178
|
-
fontWeight: '600',
|
|
2179
|
-
marginBottom: 4
|
|
2180
|
-
},
|
|
2181
2084
|
topZoneText: {
|
|
2182
2085
|
color: 'white',
|
|
2183
2086
|
fontSize: 20,
|
|
@@ -147,7 +147,7 @@ export function transformBoundsToScreen(bounds, scale, offsetX, offsetY) {
|
|
|
147
147
|
* Unified status message logic used by both voice guidance and render text.
|
|
148
148
|
* Returns the appropriate i18n key arguments for the current scan state.
|
|
149
149
|
*/
|
|
150
|
-
export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry,
|
|
150
|
+
export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t) {
|
|
151
151
|
if (nextStep === 'COMPLETED') {
|
|
152
152
|
return t('identityDocumentCamera.scanCompleted');
|
|
153
153
|
}
|
|
@@ -173,14 +173,6 @@ export function getStatusMessage(nextStep, status, detectedDocumentType, isBrigh
|
|
|
173
173
|
if (isBrightnessLow) {
|
|
174
174
|
return t('identityDocumentCamera.lowBrightness');
|
|
175
175
|
}
|
|
176
|
-
|
|
177
|
-
// Glare is a primary failure mode (reflections off the laminate/photo). It was
|
|
178
|
-
// previously detected but never communicated, so the scan just appeared to
|
|
179
|
-
// hang. Tell the user how to fix it. Ranked above blur because a glare hotspot
|
|
180
|
-
// both blocks acceptance and often reads as a "sharp" (non-blurry) frame.
|
|
181
|
-
if (isGlareDetected) {
|
|
182
|
-
return t('identityDocumentCamera.reduceGlare');
|
|
183
|
-
}
|
|
184
176
|
if (isFrameBlurry) {
|
|
185
177
|
return t('identityDocumentCamera.avoidBlur');
|
|
186
178
|
}
|
|
@@ -182,10 +182,8 @@ export default {
|
|
|
182
182
|
'identityDocumentCamera.frontSide': 'Front',
|
|
183
183
|
'identityDocumentCamera.backSide': 'Back',
|
|
184
184
|
'identityDocumentCamera.hologramCheck': 'Hologram',
|
|
185
|
-
'identityDocumentCamera.hologramProgress': 'Capturing hologram… {{current}}/{{total}}',
|
|
186
185
|
'identityDocumentCamera.keepSteady': 'Keep device steady',
|
|
187
186
|
'identityDocumentCamera.avoidBlur': 'Keep device steady',
|
|
188
|
-
'identityDocumentCamera.reduceGlare': 'Glare detected. Tilt the document or move away from direct light.',
|
|
189
187
|
'identityDocumentCamera.wrongSideFront': 'Show the front side of your document',
|
|
190
188
|
'identityDocumentCamera.wrongSideBack': 'Show the back side of your document',
|
|
191
189
|
'identityDocumentCamera.idCardDetected': 'ID detected',
|
|
@@ -182,10 +182,8 @@ export default {
|
|
|
182
182
|
'identityDocumentCamera.frontSide': 'Ön',
|
|
183
183
|
'identityDocumentCamera.backSide': 'Arka',
|
|
184
184
|
'identityDocumentCamera.hologramCheck': 'Hologram',
|
|
185
|
-
'identityDocumentCamera.hologramProgress': 'Hologram yakalanıyor… {{current}}/{{total}}',
|
|
186
185
|
'identityDocumentCamera.keepSteady': 'Cihazı sabit tutun',
|
|
187
186
|
'identityDocumentCamera.avoidBlur': 'Cihazı sabit tutun',
|
|
188
|
-
'identityDocumentCamera.reduceGlare': 'Parlama algılandı. Belgeyi hafifçe eğin veya doğrudan ışıktan uzaklaşın.',
|
|
189
187
|
'identityDocumentCamera.wrongSideFront': 'Belgenizin ön yüzünü gösterin',
|
|
190
188
|
'identityDocumentCamera.wrongSideBack': 'Belgenizin arka yüzünü gösterin',
|
|
191
189
|
'identityDocumentCamera.idCardDetected': 'Kimlik kartı algılandı',
|
package/lib/module/version.js
CHANGED
|
@@ -11,6 +11,7 @@ export declare const MIN_CARD_FACE_SIZE_PERCENT = 0.05;
|
|
|
11
11
|
export declare const MIN_MLI_FACE_SIZE_PERCENT = 0.01;
|
|
12
12
|
export declare const REQUIRED_CONSISTENT_MRZ_READS = 2;
|
|
13
13
|
export declare const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
|
|
14
|
+
export declare const MRZ_STREAK_ACCEPT_OVERRIDE = 12;
|
|
14
15
|
export declare const HELP_PROMPT_DELAY_MS = 25000;
|
|
15
16
|
export declare const SIGNATURE_REGEX: RegExp;
|
|
16
17
|
export declare const SIGNATURE_TEXT_REGEX: RegExp;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IdentityDocumentCamera.constants.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,sCAAsC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"IdentityDocumentCamera.constants.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AAEnD,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAC3C,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,gCAAgC,KAAK,CAAC;AACnD,eAAO,MAAM,wBAAwB,OAAO,CAAC;AAC7C,eAAO,MAAM,0BAA0B,OAAO,CAAC;AAC/C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,6BAA6B,IAAI,CAAC;AAC/C,eAAO,MAAM,sCAAsC,IAAI,CAAC;AASxD,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAK7C,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAE1C,eAAO,MAAM,eAAe,QAAkC,CAAC;AAC/D,eAAO,MAAM,oBAAoB,QAAyB,CAAC;AAC3D,eAAO,MAAM,iBAAiB,QAAsB,CAAC;AASrD,eAAO,MAAM,iBAAiB,QAA+B,CAAC;AAS9D,eAAO,MAAM,oBAAoB,QAAyC,CAAC;AAI3E,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAChD,eAAO,MAAM,sBAAsB;;;;;;;;;;CAUzB,CAAC;AAKX,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAC9C,eAAO,MAAM,gCAAgC,MAAM,CAAC;AACpD,eAAO,MAAM,2BAA2B,OAAO,CAAC;AAChD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,mBAAmB,MAAM,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IdentityDocumentCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.tsx"],"names":[],"mappings":"AA6EA,OAAO,KAAK,EACV,mBAAmB,EACnB,SAAS,EACT,2BAA2B,EAK5B,MAAM,gCAAgC,CAAC;AAGxC,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,QAAA,MAAM,sBAAsB,GAAI,uDAI7B,2BAA2B,
|
|
1
|
+
{"version":3,"file":"IdentityDocumentCamera.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.tsx"],"names":[],"mappings":"AA6EA,OAAO,KAAK,EACV,mBAAmB,EACnB,SAAS,EACT,2BAA2B,EAK5B,MAAM,gCAAgC,CAAC;AAGxC,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,2BAA2B,EAAE,CAAC;AAC5E,YAAY,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAMnE,QAAA,MAAM,sBAAsB,GAAI,uDAI7B,2BAA2B,4CA2uF7B,CAAC;AAmLF,eAAe,sBAAsB,CAAC"}
|
|
@@ -82,7 +82,7 @@ export declare function transformBoundsToScreen(bounds: {
|
|
|
82
82
|
* Unified status message logic used by both voice guidance and render text.
|
|
83
83
|
* Returns the appropriate i18n key arguments for the current scan state.
|
|
84
84
|
*/
|
|
85
|
-
export declare function getStatusMessage(nextStep: ScanStep, status: ScanStatus, detectedDocumentType: DocumentType, isBrightnessLow: boolean, isFrameBlurry: boolean,
|
|
85
|
+
export declare function getStatusMessage(nextStep: ScanStep, status: ScanStatus, detectedDocumentType: DocumentType, isBrightnessLow: boolean, isFrameBlurry: boolean, allElementsDetected: boolean, elementsOutsideScanArea: string[], t: (key: string, params?: Record<string, unknown>) => string): string;
|
|
86
86
|
/**
|
|
87
87
|
* Calculate angle from two points in degrees
|
|
88
88
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IdentityDocumentCamera.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,IAAI,EACL,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQpD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAMnB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAN9B,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAU;gBAGN,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACpC,OAAO,UAAQ;IAMjB,sEAAsE;IACtE,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;IAc3B,wEAAwE;IACxE,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAM3B,IAAI,OAAO,IAAI,OAAO,CAErB;CACF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,OAAO,CAGT;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAEvE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM;;;;;EAmBpB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;;;;;sBAW9C,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;EAIjE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/D,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM;;;;;EAQhB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,UAAU,EAClB,oBAAoB,EAAE,YAAY,EAClC,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,EACtB,
|
|
1
|
+
{"version":3,"file":"IdentityDocumentCamera.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Components/IdentityDocumentCamera.utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,IAAI,EACL,MAAM,gCAAgC,CAAC;AACxC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAQpD;;;;;;;;;;;GAWG;AACH,qBAAa,UAAU;IAMnB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAN9B,OAAO,CAAC,KAAK,CAAU;IACvB,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAU;gBAGN,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACpC,OAAO,UAAQ;IAMjB,sEAAsE;IACtE,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO;IAc3B,wEAAwE;IACxE,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAM3B,IAAI,OAAO,IAAI,OAAO,CAErB;CACF;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAG7E;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAC9B,OAAO,CAGT;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAEvE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM;;;;;EAmBpB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;;;;;sBAW9C,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM;EAIjE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAC/D,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM;;;;;EAQhB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,UAAU,EAClB,oBAAoB,EAAE,YAAY,EAClC,eAAe,EAAE,OAAO,EACxB,aAAa,EAAE,OAAO,EACtB,mBAAmB,EAAE,OAAO,EAC5B,uBAAuB,EAAE,MAAM,EAAE,EACjC,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,GAC3D,MAAM,CA8FR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAC5B,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3B,MAAM,CAIR;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,SAAS,EACrB,UAAU,CAAC,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,YAAY,CA6Ed;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,YAAY,EAAE,YAAY,EAC1B,cAAc,EAAE,IAAI,EAAE,EACtB,eAAe,CAAC,EAAE,SAAS,EAC3B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GACtB,YAAY,CAcd;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,OAAO,CAWrE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,GAAG,GAAG,OAAO,CAOzD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,aAAa,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACtE,eAAe,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACxE,cAAc,EAAE,OAAO,GACtB,OAAO,CAkBT"}
|
|
@@ -180,10 +180,8 @@ declare const _default: {
|
|
|
180
180
|
'identityDocumentCamera.frontSide': string;
|
|
181
181
|
'identityDocumentCamera.backSide': string;
|
|
182
182
|
'identityDocumentCamera.hologramCheck': string;
|
|
183
|
-
'identityDocumentCamera.hologramProgress': string;
|
|
184
183
|
'identityDocumentCamera.keepSteady': string;
|
|
185
184
|
'identityDocumentCamera.avoidBlur': string;
|
|
186
|
-
'identityDocumentCamera.reduceGlare': string;
|
|
187
185
|
'identityDocumentCamera.wrongSideFront': string;
|
|
188
186
|
'identityDocumentCamera.wrongSideBack': string;
|
|
189
187
|
'identityDocumentCamera.idCardDetected': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAkVE"}
|
|
@@ -180,10 +180,8 @@ declare const _default: {
|
|
|
180
180
|
'identityDocumentCamera.frontSide': string;
|
|
181
181
|
'identityDocumentCamera.backSide': string;
|
|
182
182
|
'identityDocumentCamera.hologramCheck': string;
|
|
183
|
-
'identityDocumentCamera.hologramProgress': string;
|
|
184
183
|
'identityDocumentCamera.keepSteady': string;
|
|
185
184
|
'identityDocumentCamera.avoidBlur': string;
|
|
186
|
-
'identityDocumentCamera.reduceGlare': string;
|
|
187
185
|
'identityDocumentCamera.wrongSideFront': string;
|
|
188
186
|
'identityDocumentCamera.wrongSideBack': string;
|
|
189
187
|
'identityDocumentCamera.idCardDetected': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAqVE"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "1.513.
|
|
1
|
+
export declare const SDK_VERSION = "1.513.3";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -14,6 +14,15 @@ export const MIN_MLI_FACE_SIZE_PERCENT = 0.01; // MLI (Multi Layer Image): min 1
|
|
|
14
14
|
export const REQUIRED_CONSISTENT_MRZ_READS = 2;
|
|
15
15
|
export const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
|
|
16
16
|
|
|
17
|
+
// Frames a check-digit-VALID MRZ consensus must stay UNCHANGED before it is
|
|
18
|
+
// accepted regardless of the per-cell vote-margin ("confident") gate. This
|
|
19
|
+
// unblocks a dense passport MRZ where one stubborn OCR-B look-alike keeps a
|
|
20
|
+
// single cell below the margin threshold forever — the scan would otherwise
|
|
21
|
+
// wedge on "hold steady". Set well above the aggregator's stabilityTarget (3)
|
|
22
|
+
// so the normal confident+stable path handles the common case and this only
|
|
23
|
+
// rescues the persistent-look-alike tail (~1.5s at ~8fps).
|
|
24
|
+
export const MRZ_STREAK_ACCEPT_OVERRIDE = 12;
|
|
25
|
+
|
|
17
26
|
// After this long on a single scan step without completing it, surface the
|
|
18
27
|
// "Having trouble?" prompt so the user isn't stranded on a live camera with no
|
|
19
28
|
// timeout, retry, or help. Per-step (re-armed on each step transition).
|
|
@@ -65,11 +65,11 @@ import {
|
|
|
65
65
|
HOLOGRAM_CAPTURE_INTERVAL,
|
|
66
66
|
HOLOGRAM_MAX_FRAMES_WITHOUT_FACE,
|
|
67
67
|
MIN_BRIGHTNESS_THRESHOLD,
|
|
68
|
-
MAX_BRIGHTNESS_THRESHOLD,
|
|
69
68
|
FACE_EDGE_MARGIN_PERCENT,
|
|
70
69
|
MAX_CONSECUTIVE_QUALITY_FAILURES,
|
|
71
70
|
REQUIRED_CONSISTENT_MRZ_READS,
|
|
72
71
|
REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS,
|
|
72
|
+
MRZ_STREAK_ACCEPT_OVERRIDE,
|
|
73
73
|
HELP_PROMPT_DELAY_MS,
|
|
74
74
|
SIGNATURE_TEXT_REGEX,
|
|
75
75
|
MRZ_BLOCK_PATTERN,
|
|
@@ -141,11 +141,6 @@ const IdentityDocumentCamera = ({
|
|
|
141
141
|
>(undefined);
|
|
142
142
|
const [isBrightnessLow, setIsBrightnessLow] = useState(false);
|
|
143
143
|
const [isFrameBlurry, setIsFrameBlurry] = useState(false);
|
|
144
|
-
// Glare was being silently rejected (frames dropped with no user feedback), so
|
|
145
|
-
// a user fighting reflections just saw the scan hang. Surface it so we can show
|
|
146
|
-
// an actionable "move away from glare" hint. Set at each glare rejection;
|
|
147
|
-
// cleared when a frame passes the brightness check (a fresh, glare-free frame).
|
|
148
|
-
const [isGlareDetected, setIsGlareDetected] = useState(false);
|
|
149
144
|
const [hasGuideShown, setHasGuideShown] = useState(false);
|
|
150
145
|
// "Having trouble?" escape: if a scan step doesn't complete within
|
|
151
146
|
// HELP_PROMPT_DELAY_MS the user is otherwise stuck on a live camera forever
|
|
@@ -176,7 +171,7 @@ const IdentityDocumentCamera = ({
|
|
|
176
171
|
const consecutiveQualityFailures = useRef(0);
|
|
177
172
|
const mrzDetectionCurrentRetryCount = useRef(0);
|
|
178
173
|
|
|
179
|
-
// Hysteresis for the on-screen quality guidance. Brightness/blur
|
|
174
|
+
// Hysteresis for the on-screen quality guidance. Brightness/blur are
|
|
180
175
|
// measured every frame; feeding the raw per-frame booleans straight into
|
|
181
176
|
// state made the guidance strobe (a single blurry hand-shake frame flipped
|
|
182
177
|
// "hold steady" on and off). These smooth each flag: it appears after a few
|
|
@@ -185,19 +180,11 @@ const IdentityDocumentCamera = ({
|
|
|
185
180
|
// problems), clear slow (don't flicker off on one good frame).
|
|
186
181
|
const brightnessLowFlag = useRef(new StableFlag(2, 5));
|
|
187
182
|
const blurryFlag = useRef(new StableFlag(3, 6));
|
|
188
|
-
const glareFlag = useRef(new StableFlag(2, 5));
|
|
189
|
-
// Glare is asserted by accept-time helpers that run inside the (separately
|
|
190
|
-
// scoped) handleFaceAndText callback, so it can't flow through a handler
|
|
191
|
-
// local. This ref is cleared at the top of each frame and set true if any
|
|
192
|
-
// glare check in that frame found glare; the handler reads it to feed
|
|
193
|
-
// `glareFlag`. `hadGlareText` distinguishes "card present but glaring" from
|
|
194
|
-
// an empty view so the warning only shows over an actual document.
|
|
195
|
-
const rawGlareRef = useRef(false);
|
|
196
183
|
// Whether a document was visible in the PREVIOUS frame (recognized text,
|
|
197
184
|
// barcode, or face). The quality guidance is stabilized BEFORE the quality
|
|
198
185
|
// gate (so dark/blurry frames — which the gate early-returns on — can still
|
|
199
186
|
// raise their warnings), but document-presence is only known LATER in the
|
|
200
|
-
// frame, so blur
|
|
187
|
+
// frame, so blur is gated on the previous frame's value. One-frame lag
|
|
201
188
|
// at ~8fps is imperceptible and the hysteresis smooths it.
|
|
202
189
|
const documentPresentRef = useRef(false);
|
|
203
190
|
|
|
@@ -275,7 +262,6 @@ const IdentityDocumentCamera = ({
|
|
|
275
262
|
frameH: 0,
|
|
276
263
|
hadLowBrightness: false,
|
|
277
264
|
hadBlurryFrames: false,
|
|
278
|
-
hadGlare: false,
|
|
279
265
|
});
|
|
280
266
|
const [faceDetectionEnabled, setFaceDetectionEnabled] = useState(true);
|
|
281
267
|
const faceImages = useRef<string[]>([]);
|
|
@@ -390,7 +376,6 @@ const IdentityDocumentCamera = ({
|
|
|
390
376
|
frameH: 0,
|
|
391
377
|
hadLowBrightness: false,
|
|
392
378
|
hadBlurryFrames: false,
|
|
393
|
-
hadGlare: false,
|
|
394
379
|
};
|
|
395
380
|
cachedBarcode.current = null;
|
|
396
381
|
isCompletionCallbackInvoked.current = false;
|
|
@@ -439,7 +424,6 @@ const IdentityDocumentCamera = ({
|
|
|
439
424
|
detectedDocumentType,
|
|
440
425
|
isBrightnessLow,
|
|
441
426
|
isFrameBlurry,
|
|
442
|
-
isGlareDetected,
|
|
443
427
|
allElementsDetected,
|
|
444
428
|
elementsOutsideScanArea,
|
|
445
429
|
t
|
|
@@ -462,7 +446,6 @@ const IdentityDocumentCamera = ({
|
|
|
462
446
|
hasGuideShown,
|
|
463
447
|
isBrightnessLow,
|
|
464
448
|
isFrameBlurry,
|
|
465
|
-
isGlareDetected,
|
|
466
449
|
nextStep,
|
|
467
450
|
status,
|
|
468
451
|
detectedDocumentType,
|
|
@@ -630,33 +613,6 @@ const IdentityDocumentCamera = ({
|
|
|
630
613
|
}
|
|
631
614
|
};
|
|
632
615
|
|
|
633
|
-
// Check if face image has glare (brightness exceeds threshold). Also surfaces
|
|
634
|
-
// the result to `isGlareDetected` so the user gets an actionable hint instead
|
|
635
|
-
// of a silently-stalled scan.
|
|
636
|
-
const checkFaceGlare = async (faceImage: string): Promise<boolean> => {
|
|
637
|
-
try {
|
|
638
|
-
// Check entire face region for glare
|
|
639
|
-
const hasGlare = await OpenCVModule.isRectangularRegionBright(
|
|
640
|
-
faceImage,
|
|
641
|
-
0,
|
|
642
|
-
0,
|
|
643
|
-
100, // Full face width
|
|
644
|
-
100, // Full face height
|
|
645
|
-
MAX_BRIGHTNESS_THRESHOLD
|
|
646
|
-
);
|
|
647
|
-
// OR-accumulate into the per-frame glare signal (reset at frame top); the
|
|
648
|
-
// frame handler feeds it through the stabilized UI flag. Also record the
|
|
649
|
-
// diagnostic.
|
|
650
|
-
if (hasGlare) {
|
|
651
|
-
rawGlareRef.current = true;
|
|
652
|
-
diagStats.current.hadGlare = true;
|
|
653
|
-
}
|
|
654
|
-
return hasGlare;
|
|
655
|
-
} catch (error) {
|
|
656
|
-
return false; // Assume no glare on error
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
|
|
660
616
|
// Check if face is fully visible (not cropped at edges)
|
|
661
617
|
const isFaceFullyVisible = (
|
|
662
618
|
face: Face,
|
|
@@ -673,35 +629,6 @@ const IdentityDocumentCamera = ({
|
|
|
673
629
|
);
|
|
674
630
|
};
|
|
675
631
|
|
|
676
|
-
// Check if document image has glare
|
|
677
|
-
const checkDocumentGlare = async (
|
|
678
|
-
image: string,
|
|
679
|
-
width: number,
|
|
680
|
-
height: number
|
|
681
|
-
): Promise<boolean> => {
|
|
682
|
-
try {
|
|
683
|
-
// Check center 80% region for glare (document area)
|
|
684
|
-
const hasGlare = await OpenCVModule.isRectangularRegionBright(
|
|
685
|
-
image,
|
|
686
|
-
Math.round(width * 0.1),
|
|
687
|
-
Math.round(height * 0.1),
|
|
688
|
-
Math.round(width * 0.8),
|
|
689
|
-
Math.round(height * 0.8),
|
|
690
|
-
MAX_BRIGHTNESS_THRESHOLD
|
|
691
|
-
);
|
|
692
|
-
// OR-accumulate into the per-frame glare signal (reset at frame top); the
|
|
693
|
-
// frame handler feeds it through the stabilized UI flag. Also record the
|
|
694
|
-
// diagnostic.
|
|
695
|
-
if (hasGlare) {
|
|
696
|
-
rawGlareRef.current = true;
|
|
697
|
-
diagStats.current.hadGlare = true;
|
|
698
|
-
}
|
|
699
|
-
return hasGlare;
|
|
700
|
-
} catch (error) {
|
|
701
|
-
return false;
|
|
702
|
-
}
|
|
703
|
-
};
|
|
704
|
-
|
|
705
632
|
const setNextStepAndVibrate = useCallback(
|
|
706
633
|
(
|
|
707
634
|
nextStepType:
|
|
@@ -780,12 +707,9 @@ const IdentityDocumentCamera = ({
|
|
|
780
707
|
// carried over from the previous side).
|
|
781
708
|
brightnessLowFlag.current.reset(false);
|
|
782
709
|
blurryFlag.current.reset(false);
|
|
783
|
-
glareFlag.current.reset(false);
|
|
784
|
-
rawGlareRef.current = false;
|
|
785
710
|
documentPresentRef.current = false;
|
|
786
711
|
setIsBrightnessLow(false);
|
|
787
712
|
setIsFrameBlurry(false);
|
|
788
|
-
setIsGlareDetected(false);
|
|
789
713
|
|
|
790
714
|
if (nextStepType !== 'COMPLETED') {
|
|
791
715
|
setTimeout(() => {
|
|
@@ -1189,14 +1113,12 @@ const IdentityDocumentCamera = ({
|
|
|
1189
1113
|
const faceFullyVisible =
|
|
1190
1114
|
primaryFaces[0] &&
|
|
1191
1115
|
isFaceFullyVisible(primaryFaces[0], frameWidth, frameHeight);
|
|
1192
|
-
const hasGlare = await checkFaceGlare(croppedFaces[0]);
|
|
1193
1116
|
|
|
1194
|
-
if (!faceFullyVisible
|
|
1195
|
-
// Reject
|
|
1117
|
+
if (!faceFullyVisible) {
|
|
1118
|
+
// Reject partially visible face
|
|
1196
1119
|
if (isDebugEnabled()) {
|
|
1197
1120
|
debugLog('IdentityDocumentCamera', '[FACE] Rejected', {
|
|
1198
1121
|
fullyVisible: faceFullyVisible,
|
|
1199
|
-
hasGlare,
|
|
1200
1122
|
});
|
|
1201
1123
|
}
|
|
1202
1124
|
// Continue scanning without locking this face
|
|
@@ -1252,8 +1174,23 @@ const IdentityDocumentCamera = ({
|
|
|
1252
1174
|
// vote must be unchanged across `stabilityTarget` consecutive contributing
|
|
1253
1175
|
// frames, which is exactly the "only accept once stable" guarantee.
|
|
1254
1176
|
// `addFrame` runs for every frame above, so the consensus is always current.
|
|
1177
|
+
// `consensus.stable` also requires a "confident" vote — every contested
|
|
1178
|
+
// cell's winner must lead by a margin. On a dense TD3 passport MRZ (88
|
|
1179
|
+
// cells) a single stubborn OCR-B look-alike (A↔4, S↔5) can keep one cell
|
|
1180
|
+
// at a 1-vote margin indefinitely, so `stable` never flips and the scan
|
|
1181
|
+
// wedges on "hold steady" — even though the check-digit-VALID consensus
|
|
1182
|
+
// has been UNCHANGED for many frames. Check digits are the real correctness
|
|
1183
|
+
// guarantee; a long unchanged, valid streak is conclusive. So also accept
|
|
1184
|
+
// once a valid consensus has held unchanged for MRZ_STREAK_ACCEPT_OVERRIDE
|
|
1185
|
+
// frames (well above `stabilityTarget`), independent of the margin gate.
|
|
1186
|
+
// Short streaks still require the full confident+stable gate, preserving
|
|
1187
|
+
// the early-misread protection.
|
|
1188
|
+
const mrzValidStreakSettled =
|
|
1189
|
+
parsedMRZData?.valid === true &&
|
|
1190
|
+
consensus.stableStreak >= MRZ_STREAK_ACCEPT_OVERRIDE;
|
|
1255
1191
|
const mrzStableAndValid =
|
|
1256
|
-
consensus.stable
|
|
1192
|
+
(consensus.stable || mrzValidStreakSettled) &&
|
|
1193
|
+
parsedMRZData?.valid === true;
|
|
1257
1194
|
|
|
1258
1195
|
// Surface the reliable-detection signal to the render so the UI can show a
|
|
1259
1196
|
// green "GO" the moment (and only once) the consensus is stable + valid.
|
|
@@ -1337,23 +1274,6 @@ const IdentityDocumentCamera = ({
|
|
|
1337
1274
|
return;
|
|
1338
1275
|
}
|
|
1339
1276
|
|
|
1340
|
-
// Check for glare on ID back before accepting
|
|
1341
|
-
const hasGlare = await checkDocumentGlare(
|
|
1342
|
-
image,
|
|
1343
|
-
frameWidth,
|
|
1344
|
-
frameHeight
|
|
1345
|
-
);
|
|
1346
|
-
if (hasGlare) {
|
|
1347
|
-
if (isDebugEnabled()) {
|
|
1348
|
-
debugLog(
|
|
1349
|
-
'IdentityDocumentCamera',
|
|
1350
|
-
'[ID_BACK] Rejected - glare detected'
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
|
-
setStatus('SCANNING');
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
1277
|
if (isDebugEnabled()) {
|
|
1358
1278
|
debugLog('IdentityDocumentCamera', '[ID_BACK] MRZ validated', {
|
|
1359
1279
|
documentNumber: parsedMRZData?.fields?.documentNumber,
|
|
@@ -1530,29 +1450,17 @@ const IdentityDocumentCamera = ({
|
|
|
1530
1450
|
setIsTorchOn(true);
|
|
1531
1451
|
}
|
|
1532
1452
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
faceImages.current.push(primaryFaceOnly);
|
|
1537
|
-
lastHologramCaptureTime.current = Date.now();
|
|
1538
|
-
hologramImageCountRef.current = faceImages.current.length;
|
|
1453
|
+
faceImages.current.push(primaryFaceOnly);
|
|
1454
|
+
lastHologramCaptureTime.current = Date.now();
|
|
1455
|
+
hologramImageCountRef.current = faceImages.current.length;
|
|
1539
1456
|
|
|
1540
|
-
|
|
1541
|
-
debugLog(
|
|
1542
|
-
'IdentityDocumentCamera',
|
|
1543
|
-
'[HOLOGRAM] Frame captured',
|
|
1544
|
-
{
|
|
1545
|
-
frameIndex: faceImages.current.length - 1,
|
|
1546
|
-
totalFrames: faceImages.current.length,
|
|
1547
|
-
}
|
|
1548
|
-
);
|
|
1549
|
-
}
|
|
1550
|
-
} else if (isDebugEnabled()) {
|
|
1457
|
+
if (isDebugEnabled()) {
|
|
1551
1458
|
debugLog(
|
|
1552
1459
|
'IdentityDocumentCamera',
|
|
1553
|
-
'[HOLOGRAM]
|
|
1460
|
+
'[HOLOGRAM] Frame captured',
|
|
1554
1461
|
{
|
|
1555
|
-
|
|
1462
|
+
frameIndex: faceImages.current.length - 1,
|
|
1463
|
+
totalFrames: faceImages.current.length,
|
|
1556
1464
|
}
|
|
1557
1465
|
);
|
|
1558
1466
|
}
|
|
@@ -1955,23 +1863,6 @@ const IdentityDocumentCamera = ({
|
|
|
1955
1863
|
return;
|
|
1956
1864
|
}
|
|
1957
1865
|
|
|
1958
|
-
// Check for glare on passport before accepting
|
|
1959
|
-
const hasGlare = await checkDocumentGlare(
|
|
1960
|
-
image,
|
|
1961
|
-
frameWidth,
|
|
1962
|
-
frameHeight
|
|
1963
|
-
);
|
|
1964
|
-
if (hasGlare) {
|
|
1965
|
-
if (isDebugEnabled()) {
|
|
1966
|
-
debugLog(
|
|
1967
|
-
'IdentityDocumentCamera',
|
|
1968
|
-
'[PASSPORT] Rejected - glare detected'
|
|
1969
|
-
);
|
|
1970
|
-
}
|
|
1971
|
-
continueScanning(false);
|
|
1972
|
-
return;
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
1866
|
setDetectedDocumentType('PASSPORT');
|
|
1976
1867
|
setStatus('SCANNED');
|
|
1977
1868
|
|
|
@@ -2005,23 +1896,6 @@ const IdentityDocumentCamera = ({
|
|
|
2005
1896
|
return;
|
|
2006
1897
|
}
|
|
2007
1898
|
|
|
2008
|
-
// Check for glare on ID front before accepting
|
|
2009
|
-
const hasGlare = await checkDocumentGlare(
|
|
2010
|
-
image,
|
|
2011
|
-
frameWidth,
|
|
2012
|
-
frameHeight
|
|
2013
|
-
);
|
|
2014
|
-
if (hasGlare) {
|
|
2015
|
-
if (isDebugEnabled()) {
|
|
2016
|
-
debugLog(
|
|
2017
|
-
'IdentityDocumentCamera',
|
|
2018
|
-
'[ID_FRONT] Rejected - glare detected'
|
|
2019
|
-
);
|
|
2020
|
-
}
|
|
2021
|
-
continueScanning(false);
|
|
2022
|
-
return;
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
1899
|
setDetectedDocumentType('ID_FRONT');
|
|
2026
1900
|
setStatus('SCANNED');
|
|
2027
1901
|
|
|
@@ -2136,18 +2010,6 @@ const IdentityDocumentCamera = ({
|
|
|
2136
2010
|
const rawBrightnessLow = !isRegionBright;
|
|
2137
2011
|
if (rawBrightnessLow) diagStats.current.hadLowBrightness = true;
|
|
2138
2012
|
|
|
2139
|
-
// Per-frame glare is asserted by the accept-time helpers (checkDocumentGlare
|
|
2140
|
-
// /checkFaceGlare), but those run inside handleFaceAndText which is fired
|
|
2141
|
-
// (async, un-awaited) LATER in this handler — so this frame's glare result
|
|
2142
|
-
// isn't known yet at the stabilization block below. We therefore feed the
|
|
2143
|
-
// stabilizer the PREVIOUS frame's glare signal (captured here before the
|
|
2144
|
-
// reset). At ~8fps the one-frame lag is imperceptible and the hysteresis
|
|
2145
|
-
// smooths it anyway; without this the read would always see false and the
|
|
2146
|
-
// glare warning could never fire. Reset after capturing so a now-clean
|
|
2147
|
-
// view clears (no latch).
|
|
2148
|
-
const prevFrameGlare = rawGlareRef.current;
|
|
2149
|
-
rawGlareRef.current = false;
|
|
2150
|
-
|
|
2151
2013
|
// Check blur only in center region (area of interest) to avoid false positives
|
|
2152
2014
|
// from iOS depth-of-field background blur
|
|
2153
2015
|
let isNotBlurry = true;
|
|
@@ -2197,8 +2059,8 @@ const IdentityDocumentCamera = ({
|
|
|
2197
2059
|
// MUST run BEFORE the quality-gate early-return below: a dark or blurry
|
|
2198
2060
|
// frame is exactly what makes hasAcceptableQuality false, so if this ran
|
|
2199
2061
|
// after the gate the "too dark" / "hold steady" warnings could never
|
|
2200
|
-
// raise (their triggering frames would have already returned). Blur
|
|
2201
|
-
//
|
|
2062
|
+
// raise (their triggering frames would have already returned). Blur
|
|
2063
|
+
// is gated on document presence; brightness shows regardless, since a
|
|
2202
2064
|
// dark room is worth flagging before the card is even framed.
|
|
2203
2065
|
//
|
|
2204
2066
|
// Presence combines the previous frame's confirmed value (documentPresent-
|
|
@@ -2207,7 +2069,7 @@ const IdentityDocumentCamera = ({
|
|
|
2207
2069
|
// processing). Without the raw proxy, a document that is blurry/dark from
|
|
2208
2070
|
// its very first framed moment would keep failing the quality gate and
|
|
2209
2071
|
// early-returning before documentPresentRef is ever set true — suppressing
|
|
2210
|
-
// its blur
|
|
2072
|
+
// its blur hints for ~30 frames. The proxy lets the hint fire on the
|
|
2211
2073
|
// first framed frame.
|
|
2212
2074
|
const rawDocumentPresent =
|
|
2213
2075
|
(frame.faces?.length ?? 0) > 0 ||
|
|
@@ -2217,9 +2079,6 @@ const IdentityDocumentCamera = ({
|
|
|
2217
2079
|
documentPresentRef.current || rawDocumentPresent;
|
|
2218
2080
|
setIsBrightnessLow(brightnessLowFlag.current.push(rawBrightnessLow));
|
|
2219
2081
|
setIsFrameBlurry(blurryFlag.current.push(documentWasPresent && isBlurry));
|
|
2220
|
-
setIsGlareDetected(
|
|
2221
|
-
glareFlag.current.push(documentWasPresent && prevFrameGlare)
|
|
2222
|
-
);
|
|
2223
2082
|
// -------------------------------------------------------------------
|
|
2224
2083
|
|
|
2225
2084
|
// Store quality metrics in ref for access in handleFaceAndText callback
|
|
@@ -2284,6 +2143,32 @@ const IdentityDocumentCamera = ({
|
|
|
2284
2143
|
})),
|
|
2285
2144
|
};
|
|
2286
2145
|
|
|
2146
|
+
// Isolate the MRZ text (bottom-of-document blocks matching the MRZ
|
|
2147
|
+
// pattern) and feed ONLY that to the multi-frame consensus. CRITICAL:
|
|
2148
|
+
// this must run for EVERY build, not just debug — the identical logic
|
|
2149
|
+
// used to live only inside an `if (isDebugEnabled())` overlay block, so
|
|
2150
|
+
// in release `mrzOnlyText` was never set and the aggregator fell back to
|
|
2151
|
+
// the FULL OCR text (including the passport header "PASSPORT / TÜR /
|
|
2152
|
+
// REPUBLIC OF…"). That header noise kept the per-character consensus from
|
|
2153
|
+
// ever stabilizing, so a passport wedged on "hold steady" forever in
|
|
2154
|
+
// release while working in debug/example. Passport MRZ sits low on the
|
|
2155
|
+
// page; the `y > bottom-half` filter drops the header/photo-area text.
|
|
2156
|
+
if (textBlocks.length > 0) {
|
|
2157
|
+
const bottomHalf = frame.height * 0.5;
|
|
2158
|
+
const mrzBlocks = textBlocks.filter(
|
|
2159
|
+
(block) =>
|
|
2160
|
+
block.blockFrame &&
|
|
2161
|
+
block.blockFrame.y > bottomHalf &&
|
|
2162
|
+
MRZ_BLOCK_PATTERN.test(block.text)
|
|
2163
|
+
);
|
|
2164
|
+
if (mrzBlocks.length > 0) {
|
|
2165
|
+
scannedText.mrzOnlyText = [...mrzBlocks]
|
|
2166
|
+
.sort((a, b) => (a.blockFrame?.y || 0) - (b.blockFrame?.y || 0))
|
|
2167
|
+
.map((b) => b.text)
|
|
2168
|
+
.join('\n');
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2287
2172
|
// Read barcodes directly from native ML Kit results
|
|
2288
2173
|
let barcodes: Barcode[] = [];
|
|
2289
2174
|
if (frame.barcodes) {
|
|
@@ -2800,18 +2685,6 @@ const IdentityDocumentCamera = ({
|
|
|
2800
2685
|
</TextView>
|
|
2801
2686
|
)}
|
|
2802
2687
|
|
|
2803
|
-
{/* Hologram capture progress — 16 frames are collected while the user
|
|
2804
|
-
tilts the document; show N/16 so "tilt slowly" feels responsive
|
|
2805
|
-
instead of a silent wait. */}
|
|
2806
|
-
{nextStep === 'SCAN_HOLOGRAM' && status !== 'SCANNED' && (
|
|
2807
|
-
<TextView style={styles.hologramProgress}>
|
|
2808
|
-
{t('identityDocumentCamera.hologramProgress', {
|
|
2809
|
-
current: hologramImageCount,
|
|
2810
|
-
total: HOLOGRAM_IMAGE_COUNT,
|
|
2811
|
-
})}
|
|
2812
|
-
</TextView>
|
|
2813
|
-
)}
|
|
2814
|
-
|
|
2815
2688
|
<AnimatedText
|
|
2816
2689
|
style={[
|
|
2817
2690
|
styles.topZoneText,
|
|
@@ -2827,7 +2700,7 @@ const IdentityDocumentCamera = ({
|
|
|
2827
2700
|
opacity: errorFlashAnim,
|
|
2828
2701
|
},
|
|
2829
2702
|
// 3. Warning (yellow) - quality issues
|
|
2830
|
-
(isBrightnessLow || isFrameBlurry
|
|
2703
|
+
(isBrightnessLow || isFrameBlurry) &&
|
|
2831
2704
|
styles.topZoneTextWarning,
|
|
2832
2705
|
// 4. Scanning (green) - all elements detected AND inside scan area
|
|
2833
2706
|
status === 'SCANNING' &&
|
|
@@ -2835,7 +2708,6 @@ const IdentityDocumentCamera = ({
|
|
|
2835
2708
|
elementsOutsideScanArea.length === 0 &&
|
|
2836
2709
|
!isBrightnessLow &&
|
|
2837
2710
|
!isFrameBlurry &&
|
|
2838
|
-
!isGlareDetected &&
|
|
2839
2711
|
styles.topZoneTextScanning,
|
|
2840
2712
|
// 5. Default (white) - aligning (not all detected OR elements outside scan area)
|
|
2841
2713
|
]}
|
|
@@ -2846,7 +2718,6 @@ const IdentityDocumentCamera = ({
|
|
|
2846
2718
|
detectedDocumentType,
|
|
2847
2719
|
isBrightnessLow,
|
|
2848
2720
|
isFrameBlurry,
|
|
2849
|
-
isGlareDetected,
|
|
2850
2721
|
allElementsDetected,
|
|
2851
2722
|
elementsOutsideScanArea,
|
|
2852
2723
|
t
|
|
@@ -2899,7 +2770,9 @@ const IdentityDocumentCamera = ({
|
|
|
2899
2770
|
/>
|
|
2900
2771
|
) : null}
|
|
2901
2772
|
</View>
|
|
2902
|
-
{
|
|
2773
|
+
{/* Debug overlay is DEV-only: hard-gated on __DEV__ so a release build
|
|
2774
|
+
can never render it, even if a consumer passes debug={true}. */}
|
|
2775
|
+
{__DEV__ && isDebugEnabled() && (
|
|
2903
2776
|
<DebugOverlay
|
|
2904
2777
|
nextStep={nextStep}
|
|
2905
2778
|
status={status}
|
|
@@ -2922,7 +2795,7 @@ const IdentityDocumentCamera = ({
|
|
|
2922
2795
|
elementsOutsideScanArea={elementsOutsideScanArea}
|
|
2923
2796
|
/>
|
|
2924
2797
|
)}
|
|
2925
|
-
{testMode && testModeData && (
|
|
2798
|
+
{__DEV__ && testMode && testModeData && (
|
|
2926
2799
|
<TestModePanel mrzText={testModeData.mrzText} />
|
|
2927
2800
|
)}
|
|
2928
2801
|
|
|
@@ -3070,13 +2943,6 @@ const styles = StyleSheet.create({
|
|
|
3070
2943
|
fontWeight: '500',
|
|
3071
2944
|
marginBottom: 4,
|
|
3072
2945
|
},
|
|
3073
|
-
hologramProgress: {
|
|
3074
|
-
color: '#4CAF50',
|
|
3075
|
-
fontSize: 14,
|
|
3076
|
-
textAlign: 'center',
|
|
3077
|
-
fontWeight: '600',
|
|
3078
|
-
marginBottom: 4,
|
|
3079
|
-
},
|
|
3080
2946
|
topZoneText: {
|
|
3081
2947
|
color: 'white',
|
|
3082
2948
|
fontSize: 20,
|
|
@@ -172,7 +172,6 @@ export function getStatusMessage(
|
|
|
172
172
|
detectedDocumentType: DocumentType,
|
|
173
173
|
isBrightnessLow: boolean,
|
|
174
174
|
isFrameBlurry: boolean,
|
|
175
|
-
isGlareDetected: boolean,
|
|
176
175
|
allElementsDetected: boolean,
|
|
177
176
|
elementsOutsideScanArea: string[],
|
|
178
177
|
t: (key: string, params?: Record<string, unknown>) => string
|
|
@@ -204,14 +203,6 @@ export function getStatusMessage(
|
|
|
204
203
|
return t('identityDocumentCamera.lowBrightness');
|
|
205
204
|
}
|
|
206
205
|
|
|
207
|
-
// Glare is a primary failure mode (reflections off the laminate/photo). It was
|
|
208
|
-
// previously detected but never communicated, so the scan just appeared to
|
|
209
|
-
// hang. Tell the user how to fix it. Ranked above blur because a glare hotspot
|
|
210
|
-
// both blocks acceptance and often reads as a "sharp" (non-blurry) frame.
|
|
211
|
-
if (isGlareDetected) {
|
|
212
|
-
return t('identityDocumentCamera.reduceGlare');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
206
|
if (isFrameBlurry) {
|
|
216
207
|
return t('identityDocumentCamera.avoidBlur');
|
|
217
208
|
}
|
|
@@ -238,12 +238,8 @@ export default {
|
|
|
238
238
|
'identityDocumentCamera.frontSide': 'Front',
|
|
239
239
|
'identityDocumentCamera.backSide': 'Back',
|
|
240
240
|
'identityDocumentCamera.hologramCheck': 'Hologram',
|
|
241
|
-
'identityDocumentCamera.hologramProgress':
|
|
242
|
-
'Capturing hologram… {{current}}/{{total}}',
|
|
243
241
|
'identityDocumentCamera.keepSteady': 'Keep device steady',
|
|
244
242
|
'identityDocumentCamera.avoidBlur': 'Keep device steady',
|
|
245
|
-
'identityDocumentCamera.reduceGlare':
|
|
246
|
-
'Glare detected. Tilt the document or move away from direct light.',
|
|
247
243
|
'identityDocumentCamera.wrongSideFront':
|
|
248
244
|
'Show the front side of your document',
|
|
249
245
|
'identityDocumentCamera.wrongSideBack': 'Show the back side of your document',
|
|
@@ -239,12 +239,8 @@ export default {
|
|
|
239
239
|
'identityDocumentCamera.frontSide': 'Ön',
|
|
240
240
|
'identityDocumentCamera.backSide': 'Arka',
|
|
241
241
|
'identityDocumentCamera.hologramCheck': 'Hologram',
|
|
242
|
-
'identityDocumentCamera.hologramProgress':
|
|
243
|
-
'Hologram yakalanıyor… {{current}}/{{total}}',
|
|
244
242
|
'identityDocumentCamera.keepSteady': 'Cihazı sabit tutun',
|
|
245
243
|
'identityDocumentCamera.avoidBlur': 'Cihazı sabit tutun',
|
|
246
|
-
'identityDocumentCamera.reduceGlare':
|
|
247
|
-
'Parlama algılandı. Belgeyi hafifçe eğin veya doğrudan ışıktan uzaklaşın.',
|
|
248
244
|
'identityDocumentCamera.wrongSideFront': 'Belgenizin ön yüzünü gösterin',
|
|
249
245
|
'identityDocumentCamera.wrongSideBack': 'Belgenizin arka yüzünü gösterin',
|
|
250
246
|
'identityDocumentCamera.idCardDetected': 'Kimlik kartı algılandı',
|
package/src/version.ts
CHANGED