@trustchex/react-native-sdk 1.360.0 → 1.362.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 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +0 -13
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +0 -8
- package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +59 -39
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +94 -13
- package/ios/Camera/TrustchexCameraManager.m +0 -2
- package/ios/Camera/TrustchexCameraManager.swift +0 -7
- package/ios/Camera/TrustchexCameraView.swift +16 -47
- package/ios/OpenCV/OpenCVHelper.h +17 -0
- package/ios/OpenCV/OpenCVHelper.mm +128 -0
- package/ios/OpenCV/OpenCVModule.h +6 -0
- package/ios/OpenCV/OpenCVModule.mm +141 -0
- package/ios/TrustchexSDK-Bridging-Header.h +8 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +175 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +4 -0
- package/lib/module/Shared/Components/EIDScanner.js +0 -78
- package/lib/module/Shared/Components/FaceCamera.js +6 -3
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +199 -153
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +0 -3
- package/lib/module/Shared/Config/camera-enhancement.config.js +11 -12
- package/lib/module/Shared/Libs/mrz.utils.js +265 -0
- package/lib/module/Trustchex.js +4 -0
- package/lib/module/index.js +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +0 -19
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Config/camera-enhancement.config.d.ts +10 -10
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +18 -1
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +2 -1
- package/src/Screens/Debug/MRZTestScreen.tsx +209 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +5 -0
- package/src/Shared/Components/EIDScanner.tsx +0 -53
- package/src/Shared/Components/FaceCamera.tsx +6 -3
- package/src/Shared/Components/IdentityDocumentCamera.tsx +246 -149
- package/src/Shared/Components/QrCodeScannerCamera.tsx +0 -9
- package/src/Shared/Components/TrustchexCamera.tsx +0 -20
- package/src/Shared/Config/camera-enhancement.config.ts +6 -6
- package/src/Shared/Libs/mrz.utils.ts +289 -1
- package/src/Trustchex.tsx +5 -0
- package/src/index.tsx +3 -0
- package/src/version.ts +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +0 -785
- package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +0 -419
- package/ios/MRZValidation.m +0 -39
- package/ios/MRZValidation.swift +0 -802
- package/ios/MRZValidator.swift +0 -466
|
@@ -1,9 +1,294 @@
|
|
|
1
|
+
import { parse } from 'mrz';
|
|
2
|
+
import type { MRZFields } from '../Types/mrzFields';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* MRZ Format Types according to ICAO 9303
|
|
6
|
+
*/
|
|
7
|
+
export type MRZFormat = 'TD1' | 'TD2' | 'TD3' | 'UNKNOWN';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* MRZ Validation Result
|
|
11
|
+
*/
|
|
12
|
+
export interface MRZValidationResult {
|
|
13
|
+
valid: boolean;
|
|
14
|
+
format: MRZFormat;
|
|
15
|
+
fields?: MRZFields;
|
|
16
|
+
error?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fixes raw MRZ text by removing noise and normalizing
|
|
21
|
+
* @param rawText Raw OCR text
|
|
22
|
+
* @returns Cleaned and normalized MRZ text
|
|
23
|
+
*/
|
|
24
|
+
const fixMRZ = (rawText: string): string => {
|
|
25
|
+
if (!rawText) {
|
|
26
|
+
return '';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Ensure uppercase immediately
|
|
30
|
+
rawText = rawText.toUpperCase();
|
|
31
|
+
|
|
32
|
+
const fillerChar = '<';
|
|
33
|
+
const mrzFormats = [
|
|
34
|
+
{ lines: 3, length: 30 }, // TD1
|
|
35
|
+
{ lines: 2, length: 36 }, // TD2
|
|
36
|
+
{ lines: 2, length: 44 }, // TD3
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Remove « characters, spaces, and tabs (but preserve newlines for line splitting)
|
|
40
|
+
// OCR sometimes inserts spaces within document numbers
|
|
41
|
+
let cleanedText = rawText.replace(/[« \t*]/g, '');
|
|
42
|
+
|
|
43
|
+
// Critical OCR fix: "1" at start of line is always "I" in MRZ (document code)
|
|
44
|
+
cleanedText = cleanedText.replace(/^1</gm, 'I<');
|
|
45
|
+
cleanedText = cleanedText.replace(/\n1</g, '\nI<');
|
|
46
|
+
|
|
47
|
+
// Conservative OCR error corrections - only fix clear filler character errors
|
|
48
|
+
// Don't touch valid digit sequences!
|
|
49
|
+
|
|
50
|
+
// Pattern 1: TD1 Line 2 filler area - After TUR, convert all K to < before check digit
|
|
51
|
+
// Structure: ...TUR<<<<<<<<<<<<8 (11 fillers + 1 check digit)
|
|
52
|
+
// OCR often reads: TUR<<<KKKK<<8 or TURK<<KK<<<8
|
|
53
|
+
cleanedText = cleanedText.replace(/TUR[<K]+(\d)$/gm, (match, checkDigit) => {
|
|
54
|
+
const fillerPart = match.substring(3, match.length - 1).replace(/K/g, '<');
|
|
55
|
+
return 'TUR' + fillerPart + checkDigit;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Pattern 2: Convert K at the very end of a line before check digit
|
|
59
|
+
// e.g., "<<<K8" → "<<<8" (K is misread <, 8 is check digit)
|
|
60
|
+
cleanedText = cleanedText.replace(/K(\d)$/gm, '<$1');
|
|
61
|
+
|
|
62
|
+
// Pattern 3: Convert K when surrounded by fillers only
|
|
63
|
+
// e.g., "<<K<<" → "<<<<<" (clearly a filler position)
|
|
64
|
+
cleanedText = cleanedText.replace(/(<+)K(<+)/g, '$1<$2');
|
|
65
|
+
|
|
66
|
+
// Pattern 4: Fix trailing filler area corruption at line end
|
|
67
|
+
// e.g., "TUR<<<<<KK" → "TUR<<<<<<<" (only at end after clear filler sequence)
|
|
68
|
+
cleanedText = cleanedText.replace(/(<{5,})[KGB]+$/gm, (match, fillers) => {
|
|
69
|
+
return fillers.padEnd(match.length, '<');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Split into lines and filter for MRZ-like content
|
|
73
|
+
// MRZ lines must start with I< or contain long sequences of < and alphanumeric chars
|
|
74
|
+
const lines = cleanedText
|
|
75
|
+
.split('\n')
|
|
76
|
+
.map(line => line.trim())
|
|
77
|
+
.filter(line => {
|
|
78
|
+
// Must contain filler characters
|
|
79
|
+
if (!/<+/.test(line)) return false;
|
|
80
|
+
|
|
81
|
+
// Prioritize lines starting with I< (document code)
|
|
82
|
+
if (line.startsWith('I<')) return true;
|
|
83
|
+
|
|
84
|
+
// Or lines with numbers and fillers (second line of TD1: birth date, expiry, etc.)
|
|
85
|
+
if (/\d{5,}/.test(line) && /<{3,}/.test(line)) return true;
|
|
86
|
+
|
|
87
|
+
// Or lines that look like name fields (third line of TD1: surname, given names)
|
|
88
|
+
// Accept shorter lines (min 10 chars) since OCR often truncates the third line
|
|
89
|
+
if (line.length >= 10 && /^[A-Z<]+$/.test(line) && /<{2,}/.test(line)) return true;
|
|
90
|
+
|
|
91
|
+
return false;
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (lines.length === 0) {
|
|
95
|
+
return rawText;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Detect format based on line lengths
|
|
99
|
+
let detectedFormat: { lines: number; length: number } | null = null;
|
|
100
|
+
for (const format of mrzFormats) {
|
|
101
|
+
const expectedLen = format.length;
|
|
102
|
+
// Check if all lines are within acceptable length
|
|
103
|
+
if (lines.every(line => line.length <= expectedLen + 5)) {
|
|
104
|
+
detectedFormat = format;
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!detectedFormat) {
|
|
110
|
+
return rawText;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const expectedLen = detectedFormat.length;
|
|
114
|
+
const expectedLines = detectedFormat.lines;
|
|
115
|
+
|
|
116
|
+
// Pad lines to expected length
|
|
117
|
+
const mrzLines: string[] = [];
|
|
118
|
+
for (let line of lines) {
|
|
119
|
+
if (line.includes('<')) {
|
|
120
|
+
if (line.length < expectedLen) {
|
|
121
|
+
// Pad to expected length
|
|
122
|
+
line = line.padEnd(expectedLen, fillerChar);
|
|
123
|
+
} else if (line.length > expectedLen) {
|
|
124
|
+
// Truncate to expected length
|
|
125
|
+
line = line.substring(0, expectedLen);
|
|
126
|
+
}
|
|
127
|
+
mrzLines.push(line);
|
|
128
|
+
|
|
129
|
+
// Stop when we have the expected number of lines
|
|
130
|
+
if (mrzLines.length >= expectedLines) break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (mrzLines.length < 2) {
|
|
135
|
+
return rawText;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const fixedMRZ = mrzLines.join('\n');
|
|
139
|
+
return fixedMRZ;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const AMBIGUOUS_CHAR_MAP: Record<string, string[]> = {
|
|
143
|
+
'0': ['O', 'Q', 'D'],
|
|
144
|
+
O: ['0', 'Q', 'D'],
|
|
145
|
+
Q: ['0', 'O', 'D'],
|
|
146
|
+
D: ['0', 'O', 'Q'],
|
|
147
|
+
'1': ['I', 'L'],
|
|
148
|
+
I: ['1', 'L'],
|
|
149
|
+
L: ['1', 'I'],
|
|
150
|
+
'2': ['Z'],
|
|
151
|
+
Z: ['2'],
|
|
152
|
+
'5': ['S'],
|
|
153
|
+
S: ['5'],
|
|
154
|
+
'6': ['G'],
|
|
155
|
+
G: ['6'],
|
|
156
|
+
'8': ['B'],
|
|
157
|
+
B: ['8'],
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const generateAmbiguousVariants = (
|
|
161
|
+
text: string,
|
|
162
|
+
maxChanges: number = 2,
|
|
163
|
+
maxVariants: number = 64
|
|
164
|
+
): string[] => {
|
|
165
|
+
const entries: Array<{ text: string; changes: number }> = [
|
|
166
|
+
{ text, changes: 0 },
|
|
167
|
+
];
|
|
168
|
+
const seen = new Set<string>([text]);
|
|
169
|
+
|
|
170
|
+
for (let i = 0; i < text.length; i++) {
|
|
171
|
+
const initialReplacements = AMBIGUOUS_CHAR_MAP[text[i]];
|
|
172
|
+
if (!initialReplacements) continue;
|
|
173
|
+
|
|
174
|
+
const currentLen = entries.length;
|
|
175
|
+
for (let j = 0; j < currentLen; j++) {
|
|
176
|
+
const entry = entries[j];
|
|
177
|
+
if (entry.changes >= maxChanges) continue;
|
|
178
|
+
|
|
179
|
+
const entryChar = entry.text[i];
|
|
180
|
+
const replacements = AMBIGUOUS_CHAR_MAP[entryChar];
|
|
181
|
+
if (!replacements) continue;
|
|
182
|
+
|
|
183
|
+
for (const replacement of replacements) {
|
|
184
|
+
if (replacement === entryChar) continue;
|
|
185
|
+
if (entries.length >= maxVariants) return entries.map(e => e.text);
|
|
186
|
+
|
|
187
|
+
const newText =
|
|
188
|
+
entry.text.slice(0, i) + replacement + entry.text.slice(i + 1);
|
|
189
|
+
if (!seen.has(newText)) {
|
|
190
|
+
seen.add(newText);
|
|
191
|
+
entries.push({ text: newText, changes: entry.changes + 1 });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return entries.map(e => e.text);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Validates MRZ text using the mrz npm package
|
|
202
|
+
* @param mrzText Raw or cleaned MRZ text
|
|
203
|
+
* @param autocorrect Enable autocorrection
|
|
204
|
+
* @returns Validation result with parsed fields
|
|
205
|
+
*/
|
|
206
|
+
const validateMRZ = (
|
|
207
|
+
mrzText: string,
|
|
208
|
+
autocorrect: boolean = true
|
|
209
|
+
): MRZValidationResult => {
|
|
210
|
+
try {
|
|
211
|
+
const fixedText = fixMRZ(mrzText);
|
|
212
|
+
let result = parse(fixedText, { autocorrect });
|
|
213
|
+
|
|
214
|
+
if (!result || !result.valid) {
|
|
215
|
+
const variants = generateAmbiguousVariants(fixedText);
|
|
216
|
+
for (const variant of variants) {
|
|
217
|
+
if (variant === fixedText) continue;
|
|
218
|
+
const candidate = parse(variant, { autocorrect });
|
|
219
|
+
if (candidate && candidate.valid) {
|
|
220
|
+
result = candidate;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!result || !result.valid) {
|
|
227
|
+
return {
|
|
228
|
+
valid: false,
|
|
229
|
+
format: 'UNKNOWN',
|
|
230
|
+
error: 'Invalid MRZ format or checksum',
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Map mrz package format to our format
|
|
235
|
+
let format: MRZFormat = 'UNKNOWN';
|
|
236
|
+
if (result.format === 'TD1') format = 'TD1';
|
|
237
|
+
else if (result.format === 'TD2') format = 'TD2';
|
|
238
|
+
else if (result.format === 'TD3') format = 'TD3';
|
|
239
|
+
|
|
240
|
+
// Map mrz package fields to our MRZFields type
|
|
241
|
+
const fields: MRZFields = {
|
|
242
|
+
documentCode: result.fields.documentCode || null,
|
|
243
|
+
issuingState: result.fields.issuingState || null,
|
|
244
|
+
documentNumber: result.fields.documentNumber || null,
|
|
245
|
+
documentNumberCheckDigit: result.fields.documentNumberCheckDigit || null,
|
|
246
|
+
nationality: result.fields.nationality || null,
|
|
247
|
+
lastName: result.fields.lastName || null,
|
|
248
|
+
firstName: result.fields.firstName || null,
|
|
249
|
+
sex: result.fields.sex || null,
|
|
250
|
+
birthDate: result.fields.birthDate || null,
|
|
251
|
+
birthDateCheckDigit: result.fields.birthDateCheckDigit || null,
|
|
252
|
+
expirationDate: result.fields.expirationDate || null,
|
|
253
|
+
expirationDateCheckDigit: result.fields.expirationDateCheckDigit || null,
|
|
254
|
+
personalNumber: result.fields.personalNumber || null,
|
|
255
|
+
personalNumberCheckDigit: result.fields.personalNumberCheckDigit || null,
|
|
256
|
+
compositeCheckDigit: result.fields.compositeCheckDigit || null,
|
|
257
|
+
optional1: result.fields.optional1 || null,
|
|
258
|
+
optional2: result.fields.optional2 || null,
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
valid: true,
|
|
263
|
+
format,
|
|
264
|
+
fields,
|
|
265
|
+
};
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
valid: false,
|
|
269
|
+
format: 'UNKNOWN',
|
|
270
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Validates MRZ with corrections (uses autocorrect from mrz package)
|
|
277
|
+
* @param mrzText Raw OCR text
|
|
278
|
+
* @returns Validation result with parsed fields
|
|
279
|
+
*/
|
|
280
|
+
const validateMRZWithCorrections = (
|
|
281
|
+
mrzText: string
|
|
282
|
+
): MRZValidationResult => {
|
|
283
|
+
return validateMRZ(mrzText, true);
|
|
284
|
+
};
|
|
285
|
+
|
|
1
286
|
/**
|
|
2
287
|
* Converts an MRZ date string to an ISO date string.
|
|
3
288
|
* @param mrzDate The MRZ date string to convert (YYMMDD format)
|
|
4
289
|
* @returns The ISO date string, or null if the input is invalid
|
|
5
290
|
*/
|
|
6
|
-
const convertMRZDateToISODate = (mrzDate?: string | null) => {
|
|
291
|
+
const convertMRZDateToISODate = (mrzDate?: string | null): string | null => {
|
|
7
292
|
if (!mrzDate || mrzDate.length !== 6) {
|
|
8
293
|
return null;
|
|
9
294
|
}
|
|
@@ -20,5 +305,8 @@ const convertMRZDateToISODate = (mrzDate?: string | null) => {
|
|
|
20
305
|
};
|
|
21
306
|
|
|
22
307
|
export default {
|
|
308
|
+
fixMRZ,
|
|
309
|
+
validateMRZ,
|
|
310
|
+
validateMRZWithCorrections,
|
|
23
311
|
convertMRZDateToISODate,
|
|
24
312
|
};
|
package/src/Trustchex.tsx
CHANGED
|
@@ -15,6 +15,7 @@ import ContractAcceptanceScreen from './Screens/Dynamic/ContractAcceptanceScreen
|
|
|
15
15
|
import VerificationSessionCheckScreen from './Screens/Static/VerificationSessionCheckScreen';
|
|
16
16
|
import QrCodeScanningScreen from './Screens/Static/QrCodeScanningScreen';
|
|
17
17
|
import OTPVerificationScreen from './Screens/Static/OTPVerificationScreen';
|
|
18
|
+
import MRZTestScreen from './Screens/Debug/MRZTestScreen';
|
|
18
19
|
import AppContext from './Shared/Contexts/AppContext';
|
|
19
20
|
import DebugNavigationPanel from './Shared/Components/DebugNavigationPanel';
|
|
20
21
|
import i18n from './Translation';
|
|
@@ -227,6 +228,10 @@ const Trustchex: React.FC<TrustchexProps> = ({
|
|
|
227
228
|
name="QrCodeScanningScreen"
|
|
228
229
|
component={QrCodeScanningScreen}
|
|
229
230
|
/>
|
|
231
|
+
<Stack.Screen
|
|
232
|
+
name="MRZTestScreen"
|
|
233
|
+
component={MRZTestScreen}
|
|
234
|
+
/>
|
|
230
235
|
</Stack.Navigator>
|
|
231
236
|
<DebugNavigationPanel />
|
|
232
237
|
</NavigationContainer>
|
package/src/index.tsx
CHANGED
|
@@ -48,5 +48,8 @@ export type {
|
|
|
48
48
|
ErrorContext,
|
|
49
49
|
ErrorEventMetadata,
|
|
50
50
|
} from './Shared/Types/analytics.types';
|
|
51
|
+
export { default as mrzUtils } from './Shared/Libs/mrz.utils';
|
|
52
|
+
export type { MRZFormat, MRZValidationResult } from './Shared/Libs/mrz.utils';
|
|
53
|
+
export type { MRZFields, MRZFieldName } from './Shared/Types/mrzFields';
|
|
51
54
|
|
|
52
55
|
export default Trustchex;
|
package/src/version.ts
CHANGED