@test-web/react-native-sdk 2.2.0 → 2.4.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 (78) hide show
  1. package/android/build/.transforms/246c075ea944392e66db7aa639265547/results.bin +1 -0
  2. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/BuildConfig.dex +0 -0
  3. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKModule.dex +0 -0
  4. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/yourcompany/sdk/YourSDKPackage.dex +0 -0
  5. package/android/build/.transforms/246c075ea944392e66db7aa639265547/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin +0 -0
  6. package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/results.bin +1 -0
  7. package/android/build/.transforms/952af5a0e7b5b2ac3d48ad66eccefd1f/transformed/classes/classes_dex/classes.dex +0 -0
  8. package/android/build/generated/source/buildConfig/debug/com/yourcompany/sdk/BuildConfig.java +10 -0
  9. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +34 -0
  10. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json +18 -0
  11. package/android/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties +6 -0
  12. package/android/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json +1 -0
  13. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  14. package/android/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar +0 -0
  15. package/android/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt +0 -0
  16. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -0
  17. package/android/build/intermediates/incremental/debug/packageDebugResources/merger.xml +2 -0
  18. package/android/build/intermediates/incremental/mergeDebugAssets/merger.xml +2 -0
  19. package/android/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml +2 -0
  20. package/android/build/intermediates/incremental/mergeDebugShaders/merger.xml +2 -0
  21. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/BuildConfig.class +0 -0
  22. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKModule.class +0 -0
  23. package/android/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/yourcompany/sdk/YourSDKPackage.class +0 -0
  24. package/android/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt +2 -0
  25. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +56 -0
  26. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +34 -0
  27. package/android/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json +1 -0
  28. package/android/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt +1 -0
  29. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/BuildConfig.class +0 -0
  30. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKModule.class +0 -0
  31. package/android/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/yourcompany/sdk/YourSDKPackage.class +0 -0
  32. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  33. package/android/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt +1 -0
  34. package/android/build/outputs/logs/manifest-merger-debug-report.txt +61 -0
  35. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  36. package/package.json +1 -1
  37. package/src/apis/captureBarcode.ts +37 -0
  38. package/src/apis/captureMRZ.ts +39 -0
  39. package/src/apis/checkLiveness.ts +48 -0
  40. package/src/apis/index.ts +26 -75
  41. package/src/components/common/Footer.tsx +19 -22
  42. package/src/components/common/Header.tsx +22 -21
  43. package/src/components/common/Loader.tsx +9 -28
  44. package/src/components/ui/Button.tsx +23 -31
  45. package/src/components/ui/ThemedText.tsx +21 -32
  46. package/src/context/FailedCountContext.tsx +35 -0
  47. package/src/context/IDMConfigurationContext.tsx +12 -2
  48. package/src/context/themes.ts +20 -0
  49. package/src/index.tsx +41 -29
  50. package/src/screens/BackDocumentAdvice.tsx +13 -18
  51. package/src/screens/BarcodeAdvice.tsx +39 -19
  52. package/src/screens/BarcodeCapture.tsx +127 -158
  53. package/src/screens/DocumentCaptureBack.tsx +145 -102
  54. package/src/screens/DocumentCaptureFront.tsx +146 -113
  55. package/src/screens/FrontDocumentAdvice.tsx +13 -18
  56. package/src/screens/LocationPermission.tsx +124 -17
  57. package/src/screens/MrzAdvice.tsx +27 -13
  58. package/src/screens/MrzCapture.tsx +233 -206
  59. package/src/screens/SelectDocuments.tsx +57 -66
  60. package/src/screens/SelfieAdvice.tsx +2 -3
  61. package/src/screens/SelfieCapture.tsx +135 -103
  62. package/src/screens/ThankYou.tsx +25 -31
  63. package/src/screens/VerifyIdentity.tsx +1 -0
  64. package/src/styles/BarcodeAdviceStyles.ts +6 -6
  65. package/src/styles/DocumentCaptureBackStyle.ts +70 -0
  66. package/src/styles/DocumentCaptureFrontStyle.ts +70 -0
  67. package/src/styles/FooterStyles.ts +27 -0
  68. package/src/styles/HeaderStyles.ts +20 -0
  69. package/src/styles/LoaderStyles.ts +14 -0
  70. package/src/styles/ScannerStyles.ts +46 -9
  71. package/src/styles/ThankYouStyles.ts +8 -11
  72. package/src/types/index.ts +8 -0
  73. package/src/utils/MRZ_README.md +111 -0
  74. package/src/utils/imagesHelper.ts +264 -0
  75. package/src/utils/metadata_new.json +11373 -1
  76. package/src/utils/mrzExamples.ts +127 -0
  77. package/src/utils/mrzParser.ts +303 -0
  78. package/src/utils/mrzUtils.ts +70 -0
@@ -0,0 +1,127 @@
1
+ /**
2
+ * MRZ Examples and Utilities
3
+ * Generic utilities for testing MRZ parsing (works with any country)
4
+ */
5
+
6
+ import { parseMRZ, formatMRZData } from './mrzParser';
7
+
8
+ /**
9
+ * Extract just the MRZ portion from full card text
10
+ * This helps separate the MRZ lines from other card text
11
+ */
12
+ export function extractMRZFromFullText(fullText: string): string | null {
13
+ const normalized = fullText.toUpperCase().replace(/\s/g, '');
14
+
15
+ // Try to find TD1 pattern (90 chars)
16
+ const td1Match = normalized.match(/([A-Z0-9<]{30})([A-Z0-9<]{30})([A-Z0-9<]{30})/);
17
+ if (td1Match) {
18
+ const potential = td1Match[1] + td1Match[2] + td1Match[3];
19
+ const parsed = parseMRZ(potential);
20
+ if (parsed && parsed.documentType === 'TD1') {
21
+ return potential;
22
+ }
23
+ }
24
+
25
+ // Try to find TD3 pattern (88 chars)
26
+ const td3Match = normalized.match(/([A-Z0-9<]{44})([A-Z0-9<]{44})/);
27
+ if (td3Match) {
28
+ const potential = td3Match[1] + td3Match[2];
29
+ const parsed = parseMRZ(potential);
30
+ if (parsed && parsed.documentType === 'TD3') {
31
+ return potential;
32
+ }
33
+ }
34
+
35
+ // Try to find TD2 pattern (72 chars)
36
+ const td2Match = normalized.match(/([A-Z0-9<]{36})([A-Z0-9<]{36})/);
37
+ if (td2Match) {
38
+ const potential = td2Match[1] + td2Match[2];
39
+ const parsed = parseMRZ(potential);
40
+ if (parsed && parsed.documentType === 'TD2') {
41
+ return potential;
42
+ }
43
+ }
44
+
45
+ return null;
46
+ }
47
+
48
+ /**
49
+ * Test a single MRZ string
50
+ */
51
+ export function testMRZ(mrzString: string) {
52
+ console.log('\n========== MRZ TEST ==========');
53
+ console.log('Input:', mrzString);
54
+ console.log('Length:', mrzString.length);
55
+
56
+ const parsed = parseMRZ(mrzString);
57
+
58
+ if (parsed) {
59
+ console.log('\n✓ Successfully parsed!');
60
+ console.log(formatMRZData(parsed));
61
+ } else {
62
+ console.log('\n✗ Failed to parse MRZ');
63
+ console.log('Expected lengths: 90 (TD1), 72 (TD2), or 88 (TD3)');
64
+ }
65
+
66
+ console.log('==============================\n');
67
+
68
+ return parsed;
69
+ }
70
+
71
+ /**
72
+ * Common OCR errors and corrections
73
+ */
74
+ export const OCR_CORRECTIONS = {
75
+ 'O': '0', // Letter O to zero
76
+ 'I': '1', // Letter I to one
77
+ 'S': '5', // Letter S to five
78
+ 'Z': '2', // Letter Z to two
79
+ 'B': '8', // Letter B to eight
80
+ 'G': '6', // Letter G to six
81
+ '«': '<', // Double angle quote to less than
82
+ '»': '<', // Double angle quote to less than
83
+ };
84
+
85
+ /**
86
+ * Tips for scanning MRZ codes
87
+ */
88
+ export const SCANNING_TIPS = [
89
+ 'Ensure good lighting - avoid shadows and glare',
90
+ 'Hold the document flat and steady',
91
+ 'Align the MRZ zone within the camera frame',
92
+ 'Keep the camera parallel to the document',
93
+ 'Clean the camera lens if text is blurry',
94
+ 'For passports, scan the bottom 2 lines',
95
+ 'For ID cards, scan the bottom 3 lines',
96
+ 'Avoid reflections from laminated surfaces',
97
+ ];
98
+
99
+ /**
100
+ * Document type detection guide
101
+ */
102
+ export const DOCUMENT_TYPES = {
103
+ TD1: {
104
+ name: 'TD1 - ID Card Size',
105
+ lines: 3,
106
+ charsPerLine: 30,
107
+ totalChars: 90,
108
+ examples: ['National ID Cards', 'Residence Permits', 'Some Driver Licenses'],
109
+ description: 'Used by most countries for national ID cards',
110
+ },
111
+ TD2: {
112
+ name: 'TD2 - Intermediate Size',
113
+ lines: 2,
114
+ charsPerLine: 36,
115
+ totalChars: 72,
116
+ examples: ['Some Passports', 'Travel Documents'],
117
+ description: 'Less common format for travel documents',
118
+ },
119
+ TD3: {
120
+ name: 'TD3 - Passport Size',
121
+ lines: 2,
122
+ charsPerLine: 44,
123
+ totalChars: 88,
124
+ examples: ['Passports', 'Passport Cards'],
125
+ description: 'Standard passport format used worldwide',
126
+ },
127
+ };
@@ -0,0 +1,303 @@
1
+ /**
2
+ * MRZ (Machine Readable Zone) Parser
3
+ * Supports TD1 (ID Cards), TD2, and TD3 (Passports) formats
4
+ *
5
+ * Based on ICAO Document 9303 standards
6
+ */
7
+
8
+ export interface MRZData {
9
+ documentType: 'TD1' | 'TD2' | 'TD3';
10
+ documentCode: string;
11
+ issuingCountry: string;
12
+ documentNumber: string;
13
+ dateOfBirth: string;
14
+ sex: string;
15
+ expirationDate: string;
16
+ nationality: string;
17
+ surname: string;
18
+ givenNames: string;
19
+ optionalData?: string;
20
+ rawMRZ: string;
21
+ isValid: boolean;
22
+ }
23
+
24
+ /**
25
+ * Normalize MRZ text by replacing common OCR errors
26
+ */
27
+ export function normalizeMRZ(text: string): string {
28
+ return text
29
+ .toUpperCase()
30
+ .replace(/\s/g, '')
31
+ .replace(/[«»]/g, '<')
32
+ .replace(/O/g, '0')
33
+ .replace(/[^A-Z0-9<]/g, '');
34
+ }
35
+
36
+ /**
37
+ * Calculate check digit for MRZ validation
38
+ */
39
+ function calculateCheckDigit(input: string): number {
40
+ const weights = [7, 3, 1];
41
+ let sum = 0;
42
+
43
+ for (let i = 0; i < input.length; i++) {
44
+ const char = input[i];
45
+ let value: number;
46
+
47
+ if (char === '<') {
48
+ value = 0;
49
+ } else if (char >= '0' && char <= '9') {
50
+ value = parseInt(char, 10);
51
+ } else if (char >= 'A' && char <= 'Z') {
52
+ value = char.charCodeAt(0) - 'A'.charCodeAt(0) + 10;
53
+ } else {
54
+ value = 0;
55
+ }
56
+
57
+ sum += value * weights[i % 3];
58
+ }
59
+
60
+ return sum % 10;
61
+ }
62
+
63
+ /**
64
+ * Validate check digit
65
+ */
66
+ function validateCheckDigit(data: string, checkDigit: string): boolean {
67
+ if (checkDigit === '<') return true; // Optional field
68
+ const expected = calculateCheckDigit(data);
69
+ return expected === parseInt(checkDigit, 10);
70
+ }
71
+
72
+ /**
73
+ * Parse date from MRZ format (YYMMDD)
74
+ */
75
+ function parseDate(dateStr: string): string {
76
+ if (dateStr.length !== 6) return '';
77
+
78
+ const year = parseInt(dateStr.substring(0, 2), 10);
79
+ const month = dateStr.substring(2, 4);
80
+ const day = dateStr.substring(4, 6);
81
+
82
+ // Assume dates > 50 are 19xx, otherwise 20xx
83
+ const fullYear = year > 50 ? 1900 + year : 2000 + year;
84
+
85
+ return `${fullYear}-${month}-${day}`;
86
+ }
87
+
88
+ /**
89
+ * Parse names from MRZ format
90
+ */
91
+ function parseNames(nameField: string): { surname: string; givenNames: string } {
92
+ const parts = nameField.split('<<');
93
+ const surname = parts[0].replace(/</g, ' ').trim();
94
+ const givenNames = parts.length > 1 ? parts[1].replace(/</g, ' ').trim() : '';
95
+
96
+ return { surname, givenNames };
97
+ }
98
+
99
+ /**
100
+ * Parse TD1 format (ID Cards - 3 lines of 30 characters)
101
+ * Example from Argentina ID:
102
+ * IDARG323352335<0<<<<<<<<<<<<<<<<<
103
+ * 8612179M3504037ARG<<<<<<<<<<<<8
104
+ * SALVADOR<<HECTOR<MAURICIO<<<<<<
105
+ */
106
+ export function parseTD1(mrz: string): MRZData | null {
107
+ if (mrz.length !== 90) {
108
+ return null;
109
+ }
110
+
111
+ const line1 = mrz.substring(0, 30);
112
+ const line2 = mrz.substring(30, 60);
113
+ const line3 = mrz.substring(60, 90);
114
+
115
+ // Line 1: Document code (2), Country (3), Document number (9), Check digit (1), Optional (15)
116
+ const documentCode = line1.substring(0, 2);
117
+ const issuingCountry = line1.substring(2, 5);
118
+ const documentNumber = line1.substring(5, 14).replace(/</g, '');
119
+ const docNumCheck = line1.substring(14, 15);
120
+ const optionalData1 = line1.substring(15, 30).replace(/</g, '');
121
+
122
+ // Line 2: DOB (6), DOB check (1), Sex (1), Expiry (6), Expiry check (1), Nationality (3), Optional (11), Composite check (1)
123
+ const dateOfBirth = parseDate(line2.substring(0, 6));
124
+ const dobCheck = line2.substring(6, 7);
125
+ const sex = line2.substring(7, 8);
126
+ const expirationDate = parseDate(line2.substring(8, 14));
127
+ const expCheck = line2.substring(14, 15);
128
+ const nationality = line2.substring(15, 18);
129
+ const optionalData2 = line2.substring(18, 29).replace(/</g, '');
130
+
131
+ // Line 3: Names
132
+ const { surname, givenNames } = parseNames(line3);
133
+
134
+ // Validate check digits
135
+ const isDocNumValid = validateCheckDigit(line1.substring(5, 14), docNumCheck);
136
+ const isDobValid = validateCheckDigit(line2.substring(0, 6), dobCheck);
137
+ const isExpValid = validateCheckDigit(line2.substring(8, 14), expCheck);
138
+
139
+ const isValid = isDocNumValid && isDobValid && isExpValid;
140
+
141
+ return {
142
+ documentType: 'TD1',
143
+ documentCode,
144
+ issuingCountry,
145
+ documentNumber,
146
+ dateOfBirth,
147
+ sex: sex === 'M' ? 'Male' : sex === 'F' ? 'Female' : 'Unknown',
148
+ expirationDate,
149
+ nationality,
150
+ surname,
151
+ givenNames,
152
+ optionalData: optionalData1 + optionalData2,
153
+ rawMRZ: mrz,
154
+ isValid,
155
+ };
156
+ }
157
+
158
+ /**
159
+ * Parse TD2 format (2 lines of 36 characters)
160
+ */
161
+ export function parseTD2(mrz: string): MRZData | null {
162
+ if (mrz.length !== 72) {
163
+ return null;
164
+ }
165
+
166
+ const line1 = mrz.substring(0, 36);
167
+ const line2 = mrz.substring(36, 72);
168
+
169
+ // Line 1: Document code (2), Country (3), Names (31)
170
+ const documentCode = line1.substring(0, 2);
171
+ const issuingCountry = line1.substring(2, 5);
172
+ const { surname, givenNames } = parseNames(line1.substring(5, 36));
173
+
174
+ // Line 2: Document number (9), Check (1), Nationality (3), DOB (6), Check (1), Sex (1), Expiry (6), Check (1), Optional (7), Check (1)
175
+ const documentNumber = line2.substring(0, 9).replace(/</g, '');
176
+ const docNumCheck = line2.substring(9, 10);
177
+ const nationality = line2.substring(10, 13);
178
+ const dateOfBirth = parseDate(line2.substring(13, 19));
179
+ const dobCheck = line2.substring(19, 20);
180
+ const sex = line2.substring(20, 21);
181
+ const expirationDate = parseDate(line2.substring(21, 27));
182
+ const expCheck = line2.substring(27, 28);
183
+ const optionalData = line2.substring(28, 35).replace(/</g, '');
184
+
185
+ const isDocNumValid = validateCheckDigit(line2.substring(0, 9), docNumCheck);
186
+ const isDobValid = validateCheckDigit(line2.substring(13, 19), dobCheck);
187
+ const isExpValid = validateCheckDigit(line2.substring(21, 27), expCheck);
188
+
189
+ const isValid = isDocNumValid && isDobValid && isExpValid;
190
+
191
+ return {
192
+ documentType: 'TD2',
193
+ documentCode,
194
+ issuingCountry,
195
+ documentNumber,
196
+ dateOfBirth,
197
+ sex: sex === 'M' ? 'Male' : sex === 'F' ? 'Female' : 'Unknown',
198
+ expirationDate,
199
+ nationality,
200
+ surname,
201
+ givenNames,
202
+ optionalData,
203
+ rawMRZ: mrz,
204
+ isValid,
205
+ };
206
+ }
207
+
208
+ /**
209
+ * Parse TD3 format (Passports - 2 lines of 44 characters)
210
+ * Example from US Passport:
211
+ * P<USAGATCHALIAN<<NICOLE<ANGELINE<LUCIENNE<JA
212
+ * 5778700136USA8904160F28012456149724051<605856
213
+ */
214
+ export function parseTD3(mrz: string): MRZData | null {
215
+ if (mrz.length !== 88) {
216
+ return null;
217
+ }
218
+
219
+ const line1 = mrz.substring(0, 44);
220
+ const line2 = mrz.substring(44, 88);
221
+
222
+ // Line 1: Document code (2), Country (3), Names (39)
223
+ const documentCode = line1.substring(0, 2);
224
+ const issuingCountry = line1.substring(2, 5);
225
+ const { surname, givenNames } = parseNames(line1.substring(5, 44));
226
+
227
+ // Line 2: Passport number (9), Check (1), Nationality (3), DOB (6), Check (1), Sex (1), Expiry (6), Check (1), Optional (14), Check (1), Composite check (1)
228
+ const documentNumber = line2.substring(0, 9).replace(/</g, '');
229
+ const docNumCheck = line2.substring(9, 10);
230
+ const nationality = line2.substring(10, 13);
231
+ const dateOfBirth = parseDate(line2.substring(13, 19));
232
+ const dobCheck = line2.substring(19, 20);
233
+ const sex = line2.substring(20, 21);
234
+ const expirationDate = parseDate(line2.substring(21, 27));
235
+ const expCheck = line2.substring(27, 28);
236
+ const optionalData = line2.substring(28, 42).replace(/</g, '');
237
+
238
+ const isDocNumValid = validateCheckDigit(line2.substring(0, 9), docNumCheck);
239
+ const isDobValid = validateCheckDigit(line2.substring(13, 19), dobCheck);
240
+ const isExpValid = validateCheckDigit(line2.substring(21, 27), expCheck);
241
+
242
+ const isValid = isDocNumValid && isDobValid && isExpValid;
243
+
244
+ return {
245
+ documentType: 'TD3',
246
+ documentCode,
247
+ issuingCountry,
248
+ documentNumber,
249
+ dateOfBirth,
250
+ sex: sex === 'M' ? 'Male' : sex === 'F' ? 'Female' : 'Unknown',
251
+ expirationDate,
252
+ nationality,
253
+ surname,
254
+ givenNames,
255
+ optionalData,
256
+ rawMRZ: mrz,
257
+ isValid,
258
+ };
259
+ }
260
+
261
+ /**
262
+ * Auto-detect and parse MRZ format
263
+ */
264
+ export function parseMRZ(mrzText: string): MRZData | null {
265
+ const normalized = normalizeMRZ(mrzText);
266
+
267
+ // TD1: 90 characters (3 lines x 30)
268
+ if (normalized.length === 90) {
269
+ return parseTD1(normalized);
270
+ }
271
+
272
+ // TD2: 72 characters (2 lines x 36)
273
+ if (normalized.length === 72) {
274
+ return parseTD2(normalized);
275
+ }
276
+
277
+ // TD3: 88 characters (2 lines x 44)
278
+ if (normalized.length === 88) {
279
+ return parseTD3(normalized);
280
+ }
281
+
282
+ return null;
283
+ }
284
+
285
+ /**
286
+ * Format MRZ data for display
287
+ */
288
+ export function formatMRZData(data: MRZData): string {
289
+ return `
290
+ Document Type: ${data.documentType}
291
+ Document Code: ${data.documentCode}
292
+ Issuing Country: ${data.issuingCountry}
293
+ Document Number: ${data.documentNumber}
294
+ Surname: ${data.surname}
295
+ Given Names: ${data.givenNames}
296
+ Date of Birth: ${data.dateOfBirth}
297
+ Sex: ${data.sex}
298
+ Nationality: ${data.nationality}
299
+ Expiration Date: ${data.expirationDate}
300
+ ${data.optionalData ? `Optional Data: ${data.optionalData}` : ''}
301
+ Valid: ${data.isValid ? 'Yes' : 'No'}
302
+ `.trim();
303
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * MRZ Utilities
3
+ * Based on demo-testing-sdk implementation
4
+ */
5
+
6
+ /**
7
+ * Normalize MRZ text by replacing common OCR errors
8
+ */
9
+ export function normalizeMrz(text: string): string {
10
+ return text
11
+ .replace(/\s/g, '') // Remove spaces
12
+ .replace(/[«»]/g, '<') // Replace angle quotes
13
+ .replace(/O/g, '0') // Letter O to zero
14
+ .replace(/I/g, '1') // Letter I to one
15
+ .replace(/S/g, '5') // Letter S to five
16
+ .replace(/[^A-Z0-9<]/gi, ''); // Keep only alphanumeric and <
17
+ }
18
+
19
+ /**
20
+ * Check if a line is a valid MRZ line
21
+ */
22
+ export function isMRZLine(text: string): boolean {
23
+ return /^[A-Z0-9<]{28,46}$/.test(text);
24
+ }
25
+
26
+ /**
27
+ * Extract MRZ lines from text blocks
28
+ */
29
+ export function extractMRZLines(textLines: string[]): string[] {
30
+ return textLines
31
+ .map(line => normalizeMrz(line))
32
+ .filter(line => isMRZLine(line));
33
+ }
34
+
35
+ /**
36
+ * Detect document type from MRZ lines
37
+ */
38
+ export function detectDocumentType(mrzLines: string[]): 'TD1' | 'TD2' | 'TD3' | null {
39
+ if (mrzLines.length === 0) return null;
40
+
41
+ const firstLineLength = mrzLines[0].length;
42
+
43
+ // TD1: 3 lines of 30 characters (ID cards)
44
+ if (mrzLines.length >= 3 && firstLineLength === 30) {
45
+ return 'TD1';
46
+ }
47
+
48
+ // TD3: 2 lines of 44 characters (Passports)
49
+ if (mrzLines.length >= 2 && firstLineLength === 44) {
50
+ return 'TD3';
51
+ }
52
+
53
+ // TD2: 2 lines of 36 characters
54
+ if (mrzLines.length >= 2 && firstLineLength === 36) {
55
+ return 'TD2';
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * Validate MRZ format
63
+ */
64
+ export function validateMRZ(fullText: string): boolean {
65
+ const lines = fullText.split('\n').filter(l => l.length > 0);
66
+ const mrzLines = extractMRZLines(lines);
67
+ const docType = detectDocumentType(mrzLines);
68
+
69
+ return docType !== null;
70
+ }