@trustchex/react-native-sdk 1.361.0 → 1.362.1

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.
Files changed (54) hide show
  1. package/TrustchexSDK.podspec +3 -1
  2. package/android/src/main/AndroidManifest.xml +1 -0
  3. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +0 -13
  4. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraManager.kt +0 -8
  5. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +60 -39
  6. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +94 -13
  7. package/ios/Camera/TrustchexCameraManager.m +0 -2
  8. package/ios/Camera/TrustchexCameraManager.swift +0 -7
  9. package/ios/Camera/TrustchexCameraView.swift +16 -47
  10. package/ios/OpenCV/OpenCVHelper.h +17 -0
  11. package/ios/OpenCV/OpenCVHelper.mm +128 -0
  12. package/ios/OpenCV/OpenCVModule.h +6 -0
  13. package/ios/OpenCV/OpenCVModule.mm +141 -0
  14. package/ios/TrustchexSDK-Bridging-Header.h +8 -0
  15. package/lib/module/Screens/Debug/MRZTestScreen.js +175 -0
  16. package/lib/module/Shared/Components/DebugNavigationPanel.js +4 -0
  17. package/lib/module/Shared/Components/EIDScanner.js +0 -78
  18. package/lib/module/Shared/Components/IdentityDocumentCamera.js +188 -150
  19. package/lib/module/Shared/Components/QrCodeScannerCamera.js +0 -3
  20. package/lib/module/Shared/Libs/mrz.utils.js +265 -0
  21. package/lib/module/Trustchex.js +4 -0
  22. package/lib/module/index.js +1 -0
  23. package/lib/module/version.js +1 -1
  24. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts +3 -0
  25. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -0
  26. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  27. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  28. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -1
  29. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  30. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  31. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +0 -19
  32. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
  33. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +18 -1
  34. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  35. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  36. package/lib/typescript/src/index.d.ts +3 -0
  37. package/lib/typescript/src/index.d.ts.map +1 -1
  38. package/lib/typescript/src/version.d.ts +1 -1
  39. package/package.json +2 -1
  40. package/src/Screens/Debug/MRZTestScreen.tsx +209 -0
  41. package/src/Shared/Components/DebugNavigationPanel.tsx +5 -0
  42. package/src/Shared/Components/EIDScanner.tsx +0 -53
  43. package/src/Shared/Components/IdentityDocumentCamera.tsx +228 -146
  44. package/src/Shared/Components/QrCodeScannerCamera.tsx +0 -9
  45. package/src/Shared/Components/TrustchexCamera.tsx +0 -20
  46. package/src/Shared/Libs/mrz.utils.ts +289 -1
  47. package/src/Trustchex.tsx +5 -0
  48. package/src/index.tsx +3 -0
  49. package/src/version.ts +1 -1
  50. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidationModule.kt +0 -785
  51. package/android/src/main/java/com/trustchex/reactnativesdk/mrz/MRZValidator.kt +0 -419
  52. package/ios/MRZValidation.m +0 -39
  53. package/ios/MRZValidation.swift +0 -802
  54. 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
@@ -1,3 +1,3 @@
1
1
  // This file is auto-generated. Do not edit manually.
2
2
  // Version is synced from package.json during build.
3
- export const SDK_VERSION = '1.361.0';
3
+ export const SDK_VERSION = '1.362.1';