@trustchex/react-native-sdk 1.362.4 → 1.374.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.
Files changed (32) hide show
  1. package/TrustchexSDK.podspec +3 -3
  2. package/android/build.gradle +3 -3
  3. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +71 -17
  4. package/ios/Camera/TrustchexCameraView.swift +166 -119
  5. package/lib/module/Shared/Components/FaceCamera.js +1 -0
  6. package/lib/module/Shared/Components/IdentityDocumentCamera.js +344 -207
  7. package/lib/module/Shared/Components/QrCodeScannerCamera.js +1 -8
  8. package/lib/module/Shared/Libs/mrz.utils.js +202 -9
  9. package/lib/module/Translation/Resources/en.js +0 -4
  10. package/lib/module/Translation/Resources/tr.js +0 -4
  11. package/lib/module/version.js +1 -1
  12. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  13. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  14. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  15. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +1 -0
  16. package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
  17. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts +8 -0
  18. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  19. package/lib/typescript/src/Translation/Resources/en.d.ts +0 -4
  20. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  21. package/lib/typescript/src/Translation/Resources/tr.d.ts +0 -4
  22. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  23. package/lib/typescript/src/version.d.ts +1 -1
  24. package/package.json +1 -1
  25. package/src/Shared/Components/FaceCamera.tsx +1 -0
  26. package/src/Shared/Components/IdentityDocumentCamera.tsx +443 -265
  27. package/src/Shared/Components/QrCodeScannerCamera.tsx +1 -9
  28. package/src/Shared/Components/TrustchexCamera.tsx +1 -0
  29. package/src/Shared/Libs/mrz.utils.ts +238 -26
  30. package/src/Translation/Resources/en.ts +0 -4
  31. package/src/Translation/Resources/tr.ts +0 -4
  32. package/src/version.ts +1 -1
@@ -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;
@@ -29,6 +29,9 @@ const fixMRZ = (rawText: string): string => {
29
29
  // Ensure uppercase immediately
30
30
  rawText = rawText.toUpperCase();
31
31
 
32
+ // Apply OCR-B specific corrections early
33
+ rawText = applyOCRBCorrections(rawText);
34
+
32
35
  const fillerChar = '<';
33
36
  const mrzFormats = [
34
37
  { lines: 3, length: 30 }, // TD1
@@ -44,9 +47,21 @@ const fixMRZ = (rawText: string): string => {
44
47
  cleanedText = cleanedText.replace(/^1</gm, 'I<');
45
48
  cleanedText = cleanedText.replace(/\n1</g, '\nI<');
46
49
 
50
+ // Fix lowercase letters that should be uppercase (common OCR error)
51
+ cleanedText = cleanedText
52
+ .split('\n')
53
+ .map((line) => {
54
+ // Only uppercase if line contains fillers (indicates MRZ line)
55
+ if (line.includes('<')) {
56
+ return line.toUpperCase();
57
+ }
58
+ return line;
59
+ })
60
+ .join('\n');
61
+
47
62
  // Conservative OCR error corrections - only fix clear filler character errors
48
63
  // Don't touch valid digit sequences!
49
-
64
+
50
65
  // Pattern 1: TD1 Line 2 filler area - After TUR, convert all K to < before check digit
51
66
  // Structure: ...TUR<<<<<<<<<<<<8 (11 fillers + 1 check digit)
52
67
  // OCR often reads: TUR<<<KKKK<<8 or TURK<<KK<<<8
@@ -54,40 +69,62 @@ const fixMRZ = (rawText: string): string => {
54
69
  const fillerPart = match.substring(3, match.length - 1).replace(/K/g, '<');
55
70
  return 'TUR' + fillerPart + checkDigit;
56
71
  });
57
-
72
+
58
73
  // Pattern 2: Convert K at the very end of a line before check digit
59
74
  // e.g., "<<<K8" → "<<<8" (K is misread <, 8 is check digit)
60
75
  cleanedText = cleanedText.replace(/K(\d)$/gm, '<$1');
61
-
76
+
62
77
  // Pattern 3: Convert K when surrounded by fillers only
63
78
  // e.g., "<<K<<" → "<<<<<" (clearly a filler position)
64
79
  cleanedText = cleanedText.replace(/(<+)K(<+)/g, '$1<$2');
65
-
80
+
66
81
  // Pattern 4: Fix trailing filler area corruption at line end
67
82
  // e.g., "TUR<<<<<KK" → "TUR<<<<<<<" (only at end after clear filler sequence)
68
83
  cleanedText = cleanedText.replace(/(<{5,})[KGB]+$/gm, (match, fillers) => {
69
84
  return fillers.padEnd(match.length, '<');
70
85
  });
71
86
 
87
+ // Pattern 5: Fix common OCR misreadings in filler areas
88
+ // Convert non-alphanumeric chars in filler areas to '<' (but not digits/letters in content areas)
89
+ cleanedText = cleanedText
90
+ .split('\n')
91
+ .map((line) => {
92
+ // If line has fillers, likely MRZ - safe to convert uncommon chars near fillers
93
+ if (!line.includes('<')) return line;
94
+
95
+ // Replace '.' ',' '-' with fillers in MRZ lines (OCR noise)
96
+ line = line.replace(/[.,\-~`^]/g, '<');
97
+
98
+ // Replace lowercase o/i/l with uppercase or filler depending on context
99
+ line = line.replace(/o(?=[A-Z<]|[A-Z<]o)/g, 'O'); // lowercase o surrounded by uppercase
100
+ line = line.replace(/i(?=[A-Z<]|[A-Z<]i)/g, 'I'); // lowercase i surrounded by uppercase
101
+ line = line.replace(/l(?=[A-Z<0-9])/g, 'L'); // lowercase l before uppercase or digit
102
+ line = line.replace(/l$/g, '<'); // lowercase l at line end is filler
103
+
104
+ return line;
105
+ })
106
+ .join('\n');
107
+
72
108
  // Split into lines and filter for MRZ-like content
73
109
  // MRZ lines must start with I< or contain long sequences of < and alphanumeric chars
74
110
  const lines = cleanedText
75
111
  .split('\n')
76
- .map(line => line.trim())
77
- .filter(line => {
112
+ .map((line) => line.trim())
113
+ .filter((line) => {
78
114
  // Must contain filler characters
79
115
  if (!/<+/.test(line)) return false;
80
-
116
+
81
117
  // Prioritize lines starting with I< (document code)
82
118
  if (line.startsWith('I<')) return true;
83
-
119
+
84
120
  // Or lines with numbers and fillers (second line of TD1: birth date, expiry, etc.)
85
121
  if (/\d{5,}/.test(line) && /<{3,}/.test(line)) return true;
86
-
122
+
87
123
  // Or lines that look like name fields (third line of TD1: surname, given names)
88
124
  // 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
-
125
+ if (line.length >= 10 && /^[A-Z<]+$/.test(line) && /<{2,}/.test(line))
126
+ return true;
127
+
91
128
  return false;
92
129
  });
93
130
 
@@ -100,7 +137,7 @@ const fixMRZ = (rawText: string): string => {
100
137
  for (const format of mrzFormats) {
101
138
  const expectedLen = format.length;
102
139
  // Check if all lines are within acceptable length
103
- if (lines.every(line => line.length <= expectedLen + 5)) {
140
+ if (lines.every((line) => line.length <= expectedLen + 5)) {
104
141
  detectedFormat = format;
105
142
  break;
106
143
  }
@@ -141,20 +178,127 @@ const fixMRZ = (rawText: string): string => {
141
178
 
142
179
  const AMBIGUOUS_CHAR_MAP: Record<string, string[]> = {
143
180
  '0': ['O', 'Q', 'D'],
144
- O: ['0', 'Q', 'D'],
145
- Q: ['0', 'O', 'D'],
146
- D: ['0', 'O', 'Q'],
181
+ 'O': ['0', 'Q', 'D'],
182
+ 'Q': ['0', 'O', 'D'],
183
+ 'D': ['0', 'O', 'Q'],
147
184
  '1': ['I', 'L'],
148
- I: ['1', 'L'],
149
- L: ['1', 'I'],
185
+ 'I': ['1', 'L'],
186
+ 'L': ['1', 'I'],
150
187
  '2': ['Z'],
151
- Z: ['2'],
188
+ 'Z': ['2'],
152
189
  '5': ['S'],
153
- S: ['5'],
190
+ 'S': ['5'],
154
191
  '6': ['G'],
155
- G: ['6'],
192
+ 'G': ['6'],
156
193
  '8': ['B'],
157
- B: ['8'],
194
+ 'B': ['8'],
195
+ '9': ['g'],
196
+ 'g': ['9'],
197
+ '4': ['A'],
198
+ 'A': ['4'],
199
+ };
200
+
201
+ /**
202
+ * OCR-B specific character corrections (used for MRZ on passports/IDs)
203
+ * OCR-B is monospaced and has specific glyph patterns that differ from other fonts
204
+ * Common OCR-B misreadings in document scanning context
205
+ */
206
+ const OCRB_SPECIFIC_MAP: Record<string, string> = {
207
+ // Glyph confusion in OCR-B (low contrast scanning)
208
+ Ø: '0', // Slashed zero sometimes appears as capital O with slash
209
+ ø: '0', // Lowercase variant
210
+ œ: 'O', // Ligature
211
+
212
+ // Common in monospace OCR
213
+ Ι: 'I', // Greek capital iota confused with I
214
+ ι: 'i', // Greek lowercase iota
215
+ l: 'I', // Lowercase L as I (common with serif OCR-B variants)
216
+ };
217
+
218
+ /**
219
+ * Validates OCR-B specific character patterns
220
+ * @param text Text to validate
221
+ * @returns True if text matches OCR-B patterns reasonably well
222
+ */
223
+ const isValidOCRBPattern = (text: string): boolean => {
224
+ const lines = text.split('\n').filter((l) => l.trim());
225
+
226
+ // OCR-B MRZ validation
227
+ for (const line of lines) {
228
+ if (!/<+/.test(line)) continue; // Skip non-MRZ lines
229
+
230
+ // OCR-B characteristics check:
231
+ // 1. Monospaced - all lines should have similar length (±2 chars)
232
+ // 2. Specific filler pattern - at least 3 consecutive fillers
233
+ // 3. Mixed digits and letters - checkdigits, names, dates
234
+
235
+ const fillerGroups = (line.match(/<+/g) || []).length;
236
+ const letterDigitSegments = (line.match(/[A-Z0-9]+/g) || []).length;
237
+
238
+ // Valid OCR-B should have 2-4 filler groups and 2-4 alphanumeric segments
239
+ if (fillerGroups < 1 || letterDigitSegments < 1) return false;
240
+ if (fillerGroups > 6 || letterDigitSegments > 6) return false; // Too fragmented
241
+ }
242
+
243
+ return true;
244
+ };
245
+
246
+ /**
247
+ * Applies OCR-B specific corrections to raw OCR text
248
+ * @param text Raw OCR text
249
+ * @returns Text with OCR-B corrections applied
250
+ */
251
+ const applyOCRBCorrections = (text: string): string => {
252
+ let corrected = text;
253
+
254
+ // Replace OCR-B specific misreadings
255
+ for (const [wrong, correct] of Object.entries(OCRB_SPECIFIC_MAP)) {
256
+ corrected = corrected.replace(new RegExp(wrong, 'g'), correct);
257
+ }
258
+
259
+ // Apply line-by-line OCR-B specific logic
260
+ corrected = corrected
261
+ .split('\n')
262
+ .map((line) => {
263
+ if (!line.includes('<')) return line; // Not an MRZ line
264
+
265
+ // In OCR-B MRZ, digit positions are strictly defined
266
+ // If we see obvious letter/digit confusion in digit areas, correct it
267
+
268
+ // Document number area: should be digits or fillers
269
+ // Pattern: after 2nd filler block, expect <8-9 digit chars> before next filler block
270
+ line = line.replace(
271
+ /([<]+)([A-Z])([0-9]{1,2})([<]+)/g,
272
+ (match, filler1, letter, digits, filler2) => {
273
+ // If single letter between fillers and digits, likely OCR error
274
+ if (letter === 'O') return `${filler1}0${digits}${filler2}`; // O -> 0
275
+ if (letter === 'I' || letter === 'L')
276
+ return `${filler1}1${digits}${filler2}`; // I/L -> 1
277
+ return match;
278
+ }
279
+ );
280
+
281
+ // Check digit areas (end of lines): should be single digit, not letter
282
+ line = line.replace(
283
+ /([<]+)([A-Z])(\d)?$/gm,
284
+ (match, fillers, letter, maybeDigit) => {
285
+ // If a letter appears before line end in check digit area, convert
286
+ if (letter === 'O') return `${fillers}0${maybeDigit || ''}`;
287
+ if (letter === 'I' || letter === 'L')
288
+ return `${fillers}1${maybeDigit || ''}`;
289
+ if (letter === 'S') return `${fillers}5${maybeDigit || ''}`;
290
+ if (letter === 'Z') return `${fillers}2${maybeDigit || ''}`;
291
+ if (letter === 'B') return `${fillers}8${maybeDigit || ''}`;
292
+ if (letter === 'G') return `${fillers}6${maybeDigit || ''}`;
293
+ return match;
294
+ }
295
+ );
296
+
297
+ return line;
298
+ })
299
+ .join('\n');
300
+
301
+ return corrected;
158
302
  };
159
303
 
160
304
  const generateAmbiguousVariants = (
@@ -182,7 +326,7 @@ const generateAmbiguousVariants = (
182
326
 
183
327
  for (const replacement of replacements) {
184
328
  if (replacement === entryChar) continue;
185
- if (entries.length >= maxVariants) return entries.map(e => e.text);
329
+ if (entries.length >= maxVariants) return entries.map((e) => e.text);
186
330
 
187
331
  const newText =
188
332
  entry.text.slice(0, i) + replacement + entry.text.slice(i + 1);
@@ -194,7 +338,7 @@ const generateAmbiguousVariants = (
194
338
  }
195
339
  }
196
340
 
197
- return entries.map(e => e.text);
341
+ return entries.map((e) => e.text);
198
342
  };
199
343
 
200
344
  /**
@@ -277,12 +421,76 @@ const validateMRZ = (
277
421
  * @param mrzText Raw OCR text
278
422
  * @returns Validation result with parsed fields
279
423
  */
280
- const validateMRZWithCorrections = (
281
- mrzText: string
282
- ): MRZValidationResult => {
424
+ const validateMRZWithCorrections = (mrzText: string): MRZValidationResult => {
283
425
  return validateMRZ(mrzText, true);
284
426
  };
285
427
 
428
+ /**
429
+ * Calculates quality score for recognized MRZ text (0-100)
430
+ * Higher score = higher confidence in the OCR result
431
+ * @param mrzText Recognized MRZ text
432
+ * @returns Quality score 0-100
433
+ */
434
+ const calculateMRZQualityScore = (mrzText: string): number => {
435
+ if (!mrzText) return 0;
436
+
437
+ let score = 100;
438
+ const lines = mrzText.split('\n').filter((l) => l.trim());
439
+
440
+ // Penalty for incomplete lines
441
+ if (lines.length < 2) score -= 40;
442
+ if (lines.some((l) => l.length < 30)) score -= 20;
443
+
444
+ for (const line of lines) {
445
+ // Count suspicious patterns that indicate OCR errors
446
+ const fillerCount = (line.match(/</g) || []).length;
447
+ const digitCount = (line.match(/\d/g) || []).length;
448
+ const letterCount = (line.match(/[A-Z]/g) || []).length;
449
+ const suspiciousCount = (line.match(/[KGB]{2,}|[0O]{2,}/g) || []).length;
450
+
451
+ // Penalize if too many suspicious patterns
452
+ if (suspiciousCount > 0) score -= suspiciousCount * 5;
453
+
454
+ // Penalize if ratio of fillers seems wrong (should be 40-60% for most lines)
455
+ const fillerRatio = fillerCount / line.length;
456
+ if (fillerRatio < 0.3 || fillerRatio > 0.7) score -= 10;
457
+
458
+ // Penalize if no digits where expected (birth date, expiry date, check digits)
459
+ if (letterCount > digitCount) {
460
+ // Some lines should have mostly digits (date lines)
461
+ if (line.match(/\d{5,6}/)) {
462
+ // Has valid date pattern, ok
463
+ } else if (line.length > 30 && digitCount === 0) {
464
+ score -= 15;
465
+ }
466
+ }
467
+ }
468
+
469
+ // Apply consistency bonus if lines have similar length
470
+ const lengths = lines.map((l) => l.length);
471
+ const avgLength = lengths.reduce((a, b) => a + b, 0) / lines.length;
472
+ const variance =
473
+ lengths.reduce((sum, len) => sum + Math.abs(len - avgLength), 0) /
474
+ lines.length;
475
+ if (variance < 5) score += 10;
476
+
477
+ return Math.max(0, Math.min(100, score));
478
+ };
479
+
480
+ /**
481
+ * Validates MRZ quality and returns confidence level
482
+ * @param mrzText Raw or fixed MRZ text
483
+ * @returns Object with quality score and confidence level
484
+ */
485
+ const assessMRZQuality = (mrzText: string) => {
486
+ const score = calculateMRZQualityScore(mrzText);
487
+ return {
488
+ score,
489
+ confidence: score >= 80 ? 'high' : score >= 60 ? 'medium' : 'low',
490
+ reliable: score >= 70,
491
+ };
492
+ };
493
+
286
494
  /**
287
495
  * Converts an MRZ date string to an ISO date string.
288
496
  * @param mrzDate The MRZ date string to convert (YYMMDD format)
@@ -308,5 +516,9 @@ export default {
308
516
  fixMRZ,
309
517
  validateMRZ,
310
518
  validateMRZWithCorrections,
519
+ calculateMRZQualityScore,
520
+ assessMRZQuality,
521
+ isValidOCRBPattern,
522
+ applyOCRBCorrections,
311
523
  convertMRZDateToISODate,
312
524
  };
@@ -131,10 +131,6 @@ export default {
131
131
  'identityDocumentCamera.alignIDBackSide':
132
132
  'Align the back side of your ID card',
133
133
  'identityDocumentCamera.scanCompleted': 'Scan completed!',
134
- 'identityDocumentCamera.frontSideScanned': 'Front side scanned!',
135
- 'identityDocumentCamera.passportScanned': 'Passport scanned!',
136
- 'identityDocumentCamera.backSideScanned': 'Back side scanned!',
137
- 'identityDocumentCamera.hologramVerified': 'Hologram verified!',
138
134
  'identityDocumentCamera.searchingDocument':
139
135
  'Position document within the frame',
140
136
  'identityDocumentCamera.faceDetected': 'Keep device steady...',
@@ -132,10 +132,6 @@ export default {
132
132
  'identityDocumentCamera.alignIDBackSide':
133
133
  'Kimlik kartınızın arka yüzünü hizalayın',
134
134
  'identityDocumentCamera.scanCompleted': 'Tarama tamamlandı!',
135
- 'identityDocumentCamera.frontSideScanned': 'Ön yüz tarandı!',
136
- 'identityDocumentCamera.passportScanned': 'Pasaport tarandı!',
137
- 'identityDocumentCamera.backSideScanned': 'Arka yüz tarandı!',
138
- 'identityDocumentCamera.hologramVerified': 'Hologram doğrulandı!',
139
135
  'identityDocumentCamera.searchingDocument':
140
136
  'Belgeyi çerçeve içine yerleştirin',
141
137
  'identityDocumentCamera.faceDetected': 'Cihazı sabit tutun...',
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.362.4';
3
+ export const SDK_VERSION = '1.374.0';