@trustchex/react-native-sdk 1.362.6 → 1.381.0
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/TrustchexSDK.podspec +3 -3
- package/android/build.gradle +3 -3
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +64 -19
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
- package/ios/Camera/TrustchexCameraView.swift +166 -119
- package/ios/OpenCV/OpenCVHelper.h +0 -7
- package/ios/OpenCV/OpenCVHelper.mm +0 -60
- package/ios/OpenCV/OpenCVModule.h +0 -4
- package/ios/OpenCV/OpenCVModule.mm +440 -358
- package/lib/module/Shared/Components/DebugOverlay.js +541 -0
- package/lib/module/Shared/Components/FaceCamera.js +1 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +708 -1593
- package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +1 -8
- package/lib/module/Shared/Libs/mrz.utils.js +202 -9
- package/lib/module/Translation/Resources/en.js +0 -4
- package/lib/module/Translation/Resources/tr.js +0 -4
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +1 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +0 -4
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -4
- 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/DebugOverlay.tsx +656 -0
- package/src/Shared/Components/FaceCamera.tsx +1 -0
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +1105 -2324
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
- package/src/Shared/Components/QrCodeScannerCamera.tsx +1 -9
- package/src/Shared/Components/TrustchexCamera.tsx +1 -0
- package/src/Shared/Libs/mrz.utils.ts +238 -26
- package/src/Translation/Resources/en.ts +0 -4
- package/src/Translation/Resources/tr.ts +0 -4
- package/src/version.ts +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
export type SecurityFeature =
|
|
2
|
+
| 'icao_photo' // ICAO uyumlu fotoğraf - ICAO compliant photo
|
|
3
|
+
| 'barcode' // Barkod
|
|
4
|
+
| 'hologram' // DOVID/OVD - hologram
|
|
5
|
+
| 'wet_signature' // Islak imza - wet signature
|
|
6
|
+
| 'guilloche' // Giyoş - complex line patterns
|
|
7
|
+
| 'rainbow_print' // Gökkuşağı baskı - color gradient
|
|
8
|
+
| 'ovi' // Optik değişken mürekkep - color-shifting ink
|
|
9
|
+
| 'latent_image' // Gizli görüntü - visible at angles
|
|
10
|
+
| 'microtext'; // Mikro yazı - tiny text
|
|
11
|
+
|
|
12
|
+
export type DocumentScannedData = {
|
|
13
|
+
documentType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN';
|
|
14
|
+
image: string;
|
|
15
|
+
faceImage?: string;
|
|
16
|
+
secondaryFaceImage?: string;
|
|
17
|
+
hologramImage?: string;
|
|
18
|
+
barcodeValue?: string;
|
|
19
|
+
mrzText?: string;
|
|
20
|
+
mrzFields?: MRZFields;
|
|
21
|
+
// BDDK Madde 7 - Visual security validation (NFC fallback)
|
|
22
|
+
securityFeaturesDetected?: SecurityFeature[];
|
|
23
|
+
documentTiltValidated?: boolean;
|
|
24
|
+
interactiveTouchValidated?: boolean;
|
|
25
|
+
serialNumberVerified?: boolean;
|
|
26
|
+
transitionPointsValidated?: boolean;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type BlockText = {
|
|
30
|
+
blocks: BlocksData[];
|
|
31
|
+
resultText: string;
|
|
32
|
+
mrzOnlyText?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BlocksData = {
|
|
36
|
+
blockFrame: FrameType;
|
|
37
|
+
blockCornerPoints: CornerPointsType;
|
|
38
|
+
lines: LinesData;
|
|
39
|
+
blockLanguages: string[] | [];
|
|
40
|
+
blockText: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type CornerPointsType = [{ x: number; y: number }];
|
|
44
|
+
|
|
45
|
+
export type FrameType = {
|
|
46
|
+
boundingCenterX: number;
|
|
47
|
+
boundingCenterY: number;
|
|
48
|
+
height: number;
|
|
49
|
+
width: number;
|
|
50
|
+
x: number;
|
|
51
|
+
y: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type LinesData = [
|
|
55
|
+
lineCornerPoints: CornerPointsType,
|
|
56
|
+
elements: ElementsData,
|
|
57
|
+
lineFrame: FrameType,
|
|
58
|
+
lineLanguages: string[] | [],
|
|
59
|
+
lineText: string,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
export type ElementsData = [
|
|
63
|
+
elementCornerPoints: CornerPointsType,
|
|
64
|
+
elementFrame: FrameType,
|
|
65
|
+
elementText: string,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
export type PhotoOptions = {
|
|
69
|
+
uri: string;
|
|
70
|
+
orientation?:
|
|
71
|
+
| 'landscapeRight'
|
|
72
|
+
| 'portrait'
|
|
73
|
+
| 'portraitUpsideDown'
|
|
74
|
+
| 'landscapeLeft';
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export interface IdentityDocumentCameraProps {
|
|
78
|
+
onlyMRZScan: boolean;
|
|
79
|
+
onIdentityDocumentScanned: (scannedData: DocumentScannedData) => void;
|
|
80
|
+
testMode?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface Face {
|
|
84
|
+
bounds: { x: number; y: number; width: number; height: number };
|
|
85
|
+
rollAngle?: number;
|
|
86
|
+
pitchAngle?: number;
|
|
87
|
+
yawAngle?: number;
|
|
88
|
+
leftEyeOpenProbability?: number;
|
|
89
|
+
rightEyeOpenProbability?: number;
|
|
90
|
+
smilingProbability?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export interface Barcode {
|
|
94
|
+
rawValue: string;
|
|
95
|
+
displayValue: string;
|
|
96
|
+
format: number;
|
|
97
|
+
boundingBox: { left: number; top: number; right: number; bottom: number };
|
|
98
|
+
cornerPoints: Array<{ x: number; y: number }>;
|
|
99
|
+
value?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
import type { MRZFields } from '../Types/mrzFields';
|
|
103
|
+
|
|
104
|
+
export type ScanStep =
|
|
105
|
+
| 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
106
|
+
| 'SCAN_ID_BACK'
|
|
107
|
+
| 'SCAN_HOLOGRAM'
|
|
108
|
+
| 'COMPLETED';
|
|
109
|
+
|
|
110
|
+
export type ScanStatus = 'SEARCHING' | 'SCANNING' | 'SCANNED' | 'INCORRECT';
|
|
111
|
+
|
|
112
|
+
export type DocumentType = 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN';
|
|
113
|
+
|
|
114
|
+
export type CompletedStep =
|
|
115
|
+
| 'SCAN_ID_FRONT_OR_PASSPORT'
|
|
116
|
+
| 'SCAN_ID_BACK'
|
|
117
|
+
| 'SCAN_HOLOGRAM'
|
|
118
|
+
| null;
|
|
119
|
+
|
|
120
|
+
export type BoundsWithRotation = {
|
|
121
|
+
x: number;
|
|
122
|
+
y: number;
|
|
123
|
+
width: number;
|
|
124
|
+
height: number;
|
|
125
|
+
rollAngle?: number;
|
|
126
|
+
cropPadding?: number;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export type BoundsWithAngle = {
|
|
130
|
+
x: number;
|
|
131
|
+
y: number;
|
|
132
|
+
width: number;
|
|
133
|
+
height: number;
|
|
134
|
+
angle?: number;
|
|
135
|
+
corners?: Array<{ x: number; y: number }>;
|
|
136
|
+
};
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { Dimensions } from 'react-native';
|
|
2
|
+
import type {
|
|
3
|
+
ScanStep,
|
|
4
|
+
ScanStatus,
|
|
5
|
+
DocumentType,
|
|
6
|
+
Face,
|
|
7
|
+
} from './IdentityDocumentCamera.types';
|
|
8
|
+
import type { MRZFields } from '../Types/mrzFields';
|
|
9
|
+
import {
|
|
10
|
+
SIGNATURE_REGEX,
|
|
11
|
+
PASSPORT_MRZ_PATTERN,
|
|
12
|
+
} from './IdentityDocumentCamera.constants';
|
|
13
|
+
import { debugLog, isDebugEnabled } from '../Libs/debug.utils';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Frame-to-screen coordinate transform using FILL_CENTER scaling
|
|
17
|
+
*/
|
|
18
|
+
export function getFrameToScreenTransform(
|
|
19
|
+
frameWidth: number,
|
|
20
|
+
frameHeight: number
|
|
21
|
+
) {
|
|
22
|
+
const screen = Dimensions.get('window');
|
|
23
|
+
const frameAspect = frameWidth / frameHeight;
|
|
24
|
+
const screenAspect = screen.width / screen.height;
|
|
25
|
+
|
|
26
|
+
let scale: number;
|
|
27
|
+
let offsetX = 0;
|
|
28
|
+
let offsetY = 0;
|
|
29
|
+
|
|
30
|
+
if (frameAspect > screenAspect) {
|
|
31
|
+
scale = screen.height / frameHeight;
|
|
32
|
+
offsetX = (frameWidth * scale - screen.width) / 2;
|
|
33
|
+
} else {
|
|
34
|
+
scale = screen.width / frameWidth;
|
|
35
|
+
offsetY = (frameHeight * scale - screen.height) / 2;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return { scale, offsetX, offsetY, screen };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Compute scan area bounds in frame coordinates
|
|
43
|
+
*/
|
|
44
|
+
export function getScanAreaBounds(frameWidth: number, frameHeight: number) {
|
|
45
|
+
const { scale, offsetX, offsetY, screen } = getFrameToScreenTransform(
|
|
46
|
+
frameWidth,
|
|
47
|
+
frameHeight
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const scanLeft = (screen.width * 0.05 + offsetX) / scale;
|
|
51
|
+
const scanTop = (screen.height * 0.36 + offsetY) / scale;
|
|
52
|
+
const scanRight = (screen.width * 0.95 + offsetX) / scale;
|
|
53
|
+
const scanBottom = (screen.height * 0.64 + offsetY) / scale;
|
|
54
|
+
|
|
55
|
+
const isInsideScan = (x: number, y: number, w: number, h: number) =>
|
|
56
|
+
x >= scanLeft && y >= scanTop && x + w <= scanRight && y + h <= scanBottom;
|
|
57
|
+
|
|
58
|
+
return { scanLeft, scanTop, scanRight, scanBottom, isInsideScan };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Transform frame-space bounds to screen-space bounds
|
|
63
|
+
*/
|
|
64
|
+
export function transformBoundsToScreen(
|
|
65
|
+
bounds: { x: number; y: number; width: number; height: number },
|
|
66
|
+
scale: number,
|
|
67
|
+
offsetX: number,
|
|
68
|
+
offsetY: number
|
|
69
|
+
) {
|
|
70
|
+
return {
|
|
71
|
+
x: bounds.x * scale - offsetX,
|
|
72
|
+
y: bounds.y * scale - offsetY,
|
|
73
|
+
width: bounds.width * scale,
|
|
74
|
+
height: bounds.height * scale,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Unified status message logic used by both voice guidance and render text.
|
|
80
|
+
* Returns the appropriate i18n key arguments for the current scan state.
|
|
81
|
+
*/
|
|
82
|
+
export function getStatusMessage(
|
|
83
|
+
nextStep: ScanStep,
|
|
84
|
+
status: ScanStatus,
|
|
85
|
+
detectedDocumentType: DocumentType,
|
|
86
|
+
isBrightnessLow: boolean,
|
|
87
|
+
isFrameBlurry: boolean,
|
|
88
|
+
allElementsDetected: boolean,
|
|
89
|
+
elementsOutsideScanArea: string[],
|
|
90
|
+
t: (key: string, params?: Record<string, unknown>) => string
|
|
91
|
+
): string {
|
|
92
|
+
if (nextStep === 'COMPLETED') {
|
|
93
|
+
return t('identityDocumentCamera.scanCompleted');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (status === 'INCORRECT') {
|
|
97
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
98
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
99
|
+
}
|
|
100
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
101
|
+
return t('identityDocumentCamera.alignIDBack');
|
|
102
|
+
}
|
|
103
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
104
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
105
|
+
}
|
|
106
|
+
return t('identityDocumentCamera.alignPhotoSide');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isBrightnessLow) {
|
|
110
|
+
return t('identityDocumentCamera.lowBrightness');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (isFrameBlurry) {
|
|
114
|
+
return t('identityDocumentCamera.avoidBlur');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
status === 'SCANNING' &&
|
|
119
|
+
allElementsDetected &&
|
|
120
|
+
elementsOutsideScanArea.length === 0
|
|
121
|
+
) {
|
|
122
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
123
|
+
return t('identityDocumentCamera.idCardBackDetected');
|
|
124
|
+
}
|
|
125
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
126
|
+
return t('identityDocumentCamera.passportDetected');
|
|
127
|
+
}
|
|
128
|
+
if (detectedDocumentType === 'ID_FRONT') {
|
|
129
|
+
return t('identityDocumentCamera.idCardFrontDetected');
|
|
130
|
+
}
|
|
131
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
132
|
+
return t('identityDocumentCamera.alignHologram');
|
|
133
|
+
}
|
|
134
|
+
return t('identityDocumentCamera.readingDocument');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (elementsOutsideScanArea.length > 0) {
|
|
138
|
+
return t('identityDocumentCamera.centerDocument');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (
|
|
142
|
+
(status === 'SCANNING' || status === 'SEARCHING') &&
|
|
143
|
+
!allElementsDetected
|
|
144
|
+
) {
|
|
145
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
146
|
+
return t('identityDocumentCamera.alignIDBack');
|
|
147
|
+
}
|
|
148
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
149
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
150
|
+
return t('identityDocumentCamera.alignPassport');
|
|
151
|
+
}
|
|
152
|
+
if (detectedDocumentType === 'ID_FRONT') {
|
|
153
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
154
|
+
}
|
|
155
|
+
return t('identityDocumentCamera.alignPhotoSide');
|
|
156
|
+
}
|
|
157
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
158
|
+
return t('identityDocumentCamera.alignHologram');
|
|
159
|
+
}
|
|
160
|
+
return t('identityDocumentCamera.readingDocument');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Default fallback
|
|
164
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
165
|
+
return status === 'SCANNING'
|
|
166
|
+
? t('identityDocumentCamera.readingDocument')
|
|
167
|
+
: t('identityDocumentCamera.alignPhotoSide');
|
|
168
|
+
}
|
|
169
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
170
|
+
return t('identityDocumentCamera.alignHologram');
|
|
171
|
+
}
|
|
172
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
173
|
+
return status === 'SCANNING'
|
|
174
|
+
? t('identityDocumentCamera.readingDocument')
|
|
175
|
+
: t('identityDocumentCamera.alignIDBackSide');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calculate angle from two points in degrees
|
|
183
|
+
*/
|
|
184
|
+
export function angleBetweenPoints(
|
|
185
|
+
p1: { x: number; y: number },
|
|
186
|
+
p2: { x: number; y: number }
|
|
187
|
+
): number {
|
|
188
|
+
const dx = p2.x - p1.x;
|
|
189
|
+
const dy = p2.y - p1.y;
|
|
190
|
+
return Math.atan2(dy, dx) * (180 / Math.PI);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Calculate distance between two points
|
|
195
|
+
*/
|
|
196
|
+
export function distanceBetweenPoints(
|
|
197
|
+
p1: { x: number; y: number },
|
|
198
|
+
p2: { x: number; y: number }
|
|
199
|
+
): number {
|
|
200
|
+
const dx = p2.x - p1.x;
|
|
201
|
+
const dy = p2.y - p1.y;
|
|
202
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect document type based on faces, OCR text, and MRZ fields
|
|
207
|
+
*/
|
|
208
|
+
export function detectDocumentType(
|
|
209
|
+
faces: Face[],
|
|
210
|
+
ocrText: string,
|
|
211
|
+
mrzFields?: MRZFields,
|
|
212
|
+
frameWidth?: number,
|
|
213
|
+
mrzText?: string | null
|
|
214
|
+
): DocumentType {
|
|
215
|
+
// Relaxed signature detection: matches signature/imza variants and OCR errors
|
|
216
|
+
const hasSignatureMatch = SIGNATURE_REGEX.test(ocrText);
|
|
217
|
+
|
|
218
|
+
if (isDebugEnabled()) {
|
|
219
|
+
debugLog('IdentityDocumentCamera.utils', '[DocType] Detection', {
|
|
220
|
+
faces: faces.length,
|
|
221
|
+
hasMRZ: !!mrzFields,
|
|
222
|
+
hasMRZText: !!mrzText,
|
|
223
|
+
textLength: ocrText?.length,
|
|
224
|
+
hasSignature: hasSignatureMatch,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// ID Back: no face + ID MRZ
|
|
229
|
+
if (faces.length === 0 && mrzFields?.documentCode === 'I') {
|
|
230
|
+
return 'ID_BACK';
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Passport: face + passport MRZ
|
|
234
|
+
if (faces.length > 0 && mrzFields?.documentCode === 'P') {
|
|
235
|
+
return 'PASSPORT';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Check for PASSPORT MRZ pattern BEFORE classifying as ID_FRONT
|
|
239
|
+
// Both passports and ID cards have face + signature, so we MUST check MRZ first
|
|
240
|
+
// Check BOTH parsed mrzText AND raw ocrText — MRZ parsing can fail while
|
|
241
|
+
// the raw OCR still contains P<TUR/P<USA pattern
|
|
242
|
+
const hasPassportMRZPattern =
|
|
243
|
+
(mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText)) ||
|
|
244
|
+
PASSPORT_MRZ_PATTERN.test(ocrText);
|
|
245
|
+
|
|
246
|
+
if (hasPassportMRZPattern) {
|
|
247
|
+
// Passport MRZ pattern detected (P<TUR, P<USA, etc.)
|
|
248
|
+
// Even if not fully parsed, this is definitely a passport, not ID card
|
|
249
|
+
return 'PASSPORT';
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ID Front: face detected
|
|
253
|
+
// CRITICAL: Only classify as ID_FRONT when we're confident it's NOT a passport
|
|
254
|
+
// This means we must have either:
|
|
255
|
+
// 1. MRZ code 'I' (definitive ID card), OR
|
|
256
|
+
// 2. Face + signature but NO passport MRZ pattern visible
|
|
257
|
+
if (faces.length > 0 && ocrText?.length >= 5) {
|
|
258
|
+
// Filter to card-sized faces only (min 5% of frame width to exclude tiny background faces)
|
|
259
|
+
const cardSizedFaces = frameWidth
|
|
260
|
+
? faces.filter(
|
|
261
|
+
(face) =>
|
|
262
|
+
face.bounds.width >= frameWidth * 0.05 &&
|
|
263
|
+
face.bounds.height >= frameWidth * 0.05
|
|
264
|
+
)
|
|
265
|
+
: faces;
|
|
266
|
+
|
|
267
|
+
if (cardSizedFaces.length > 0) {
|
|
268
|
+
// If we have MRZ code 'I', it's definitely an ID card
|
|
269
|
+
if (mrzFields?.documentCode === 'I') {
|
|
270
|
+
return 'ID_FRONT';
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// If signature present and NO passport MRZ pattern, likely ID_FRONT
|
|
274
|
+
// But we can't be 100% sure yet - passport MRZ might appear in next frames
|
|
275
|
+
const hasSignature = hasSignatureMatch;
|
|
276
|
+
if (hasSignature && !hasPassportMRZPattern) {
|
|
277
|
+
return 'ID_FRONT';
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return 'UNKNOWN';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Determine the document type to set based on current frame analysis
|
|
287
|
+
* Handles correction of misdetections and passport pattern checking
|
|
288
|
+
*/
|
|
289
|
+
export function determineDocumentTypeToSet(
|
|
290
|
+
documentType: DocumentType,
|
|
291
|
+
cardSizedFaces: Face[],
|
|
292
|
+
parsedMRZFields?: MRZFields,
|
|
293
|
+
mrzText?: string | null
|
|
294
|
+
): DocumentType {
|
|
295
|
+
// CRITICAL: Passport MRZ always takes precedence over other detections
|
|
296
|
+
const hasPassportMRZ =
|
|
297
|
+
(mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText)) ||
|
|
298
|
+
parsedMRZFields?.documentCode === 'P';
|
|
299
|
+
|
|
300
|
+
if (hasPassportMRZ) {
|
|
301
|
+
return 'PASSPORT';
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// After passport MRZ check, accept the detected type directly
|
|
305
|
+
if (documentType === 'PASSPORT') return 'PASSPORT';
|
|
306
|
+
if (documentType === 'ID_FRONT') return 'ID_FRONT';
|
|
307
|
+
return 'UNKNOWN';
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Compare MRZ field values (ignore raw text variations)
|
|
312
|
+
*/
|
|
313
|
+
export function areMRZFieldsEqual(fields1: any, fields2: any): boolean {
|
|
314
|
+
if (!fields1 || !fields2) return false;
|
|
315
|
+
// Compare critical fields that define document identity
|
|
316
|
+
return (
|
|
317
|
+
fields1.documentNumber === fields2.documentNumber &&
|
|
318
|
+
fields1.birthDate === fields2.birthDate &&
|
|
319
|
+
fields1.expirationDate === fields2.expirationDate &&
|
|
320
|
+
fields1.firstName === fields2.firstName &&
|
|
321
|
+
fields1.lastName === fields2.lastName &&
|
|
322
|
+
fields1.issuingState === fields2.issuingState
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Check if all required MRZ fields are present
|
|
328
|
+
*/
|
|
329
|
+
export function hasRequiredMRZFields(fields: any): boolean {
|
|
330
|
+
return (
|
|
331
|
+
!!fields?.firstName &&
|
|
332
|
+
!!fields?.lastName &&
|
|
333
|
+
!!fields?.documentNumber &&
|
|
334
|
+
!!fields?.birthDate
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Validate if face position has changed within acceptable tolerance
|
|
340
|
+
* Returns true if position is valid (within tolerance)
|
|
341
|
+
*/
|
|
342
|
+
export function validateFacePosition(
|
|
343
|
+
currentBounds: { x: number; y: number; width: number; height: number },
|
|
344
|
+
referenceBounds: { x: number; y: number; width: number; height: number },
|
|
345
|
+
isHologramStep: boolean
|
|
346
|
+
): boolean {
|
|
347
|
+
const xDiff = Math.abs(currentBounds.x - referenceBounds.x);
|
|
348
|
+
const yDiff = Math.abs(currentBounds.y - referenceBounds.y);
|
|
349
|
+
const widthDiff = Math.abs(currentBounds.width - referenceBounds.width);
|
|
350
|
+
const heightDiff = Math.abs(currentBounds.height - referenceBounds.height);
|
|
351
|
+
|
|
352
|
+
// Use looser tolerance during hologram step since flash toggling causes position jitter
|
|
353
|
+
const tolerance = isHologramStep ? 0.5 : 0.2;
|
|
354
|
+
const xTolerance = referenceBounds.width * tolerance;
|
|
355
|
+
const yTolerance = referenceBounds.height * tolerance;
|
|
356
|
+
const sizeTolerance = referenceBounds.width * tolerance;
|
|
357
|
+
|
|
358
|
+
return (
|
|
359
|
+
xDiff <= xTolerance &&
|
|
360
|
+
yDiff <= yTolerance &&
|
|
361
|
+
widthDiff <= sizeTolerance &&
|
|
362
|
+
heightDiff <= sizeTolerance
|
|
363
|
+
);
|
|
364
|
+
}
|
|
@@ -20,7 +20,6 @@ import { useKeepAwake } from '../Libs/native-keep-awake.utils';
|
|
|
20
20
|
import { useIsFocused } from '@react-navigation/native';
|
|
21
21
|
import { useTranslation } from 'react-i18next';
|
|
22
22
|
import { debugLog, logError } from '../Libs/debug.utils';
|
|
23
|
-
import LottieView from 'lottie-react-native';
|
|
24
23
|
import StyledButton from './StyledButton';
|
|
25
24
|
import { useTheme } from '../Contexts/ThemeContext';
|
|
26
25
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
@@ -193,14 +192,7 @@ const QrCodeScannerCamera = ({
|
|
|
193
192
|
<View style={styles.leftZone} />
|
|
194
193
|
<View style={styles.rightZone} />
|
|
195
194
|
<View style={styles.bottomZone} />
|
|
196
|
-
<View style={styles.scanArea}
|
|
197
|
-
<LottieView
|
|
198
|
-
source={require('../../Shared/Animations/scanning.json')}
|
|
199
|
-
style={styles.animation}
|
|
200
|
-
loop={true}
|
|
201
|
-
autoPlay
|
|
202
|
-
/>
|
|
203
|
-
</View>
|
|
195
|
+
<View style={styles.scanArea} />
|
|
204
196
|
{onClose && (
|
|
205
197
|
<TouchableOpacity
|
|
206
198
|
onPress={onClose}
|
|
@@ -102,6 +102,7 @@ interface TrustchexCameraProps {
|
|
|
102
102
|
torchEnabled?: boolean;
|
|
103
103
|
enableFrameProcessing?: boolean;
|
|
104
104
|
targetFps?: number;
|
|
105
|
+
resolution?: string;
|
|
105
106
|
// ML Kit detection modes — processing runs natively, results arrive in Frame
|
|
106
107
|
enableFaceDetection?: boolean;
|
|
107
108
|
enableTextRecognition?: boolean;
|