@trustchex/react-native-sdk 1.487.0 → 1.489.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 (83) hide show
  1. package/ios/ImageDecoder/ImageDecoderModule.m +12 -0
  2. package/ios/ImageDecoder/ImageDecoderModule.swift +43 -0
  3. package/lib/module/Shared/Components/EIDScanner.js +100 -37
  4. package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +9 -1
  5. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +22 -2
  6. package/lib/module/Shared/Components/IdentityDocumentCamera.js +1 -1
  7. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +7 -7
  8. package/lib/module/Shared/EIDReader/cardFileInputStream.js +25 -2
  9. package/lib/module/Shared/EIDReader/defaultFileSystem.js +56 -6
  10. package/lib/module/Shared/EIDReader/eidReader.js +138 -16
  11. package/lib/module/Shared/EIDReader/lds/icao/dg11File.js +23 -6
  12. package/lib/module/Shared/EIDReader/nfcManagerCardService.js +79 -9
  13. package/lib/module/Shared/EIDReader/protocol/paceDH.js +101 -0
  14. package/lib/module/Shared/EIDReader/protocol/paceErrors.js +34 -0
  15. package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +53 -4
  16. package/lib/module/Shared/EIDReader/protocol/readBinaryAPDUSender.js +7 -3
  17. package/lib/module/Shared/Libs/country-display.utils.js +13 -1
  18. package/lib/module/Shared/Libs/diagnosticReport.js +24 -1
  19. package/lib/module/Shared/Libs/diagnostics.js +30 -8
  20. package/lib/module/Shared/Libs/jp2Decode.js +43 -0
  21. package/lib/module/Shared/Libs/mrz.utils.js +22 -5
  22. package/lib/module/Shared/Libs/mrzTransliteration.js +14 -1
  23. package/lib/module/Translation/Resources/en.js +12 -0
  24. package/lib/module/Translation/Resources/tr.js +12 -0
  25. package/lib/module/version.js +1 -1
  26. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  27. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -1
  28. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  29. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +11 -0
  30. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
  31. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +1 -1
  32. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
  33. package/lib/typescript/src/Shared/EIDReader/cardFileInputStream.d.ts +3 -0
  34. package/lib/typescript/src/Shared/EIDReader/cardFileInputStream.d.ts.map +1 -1
  35. package/lib/typescript/src/Shared/EIDReader/defaultFileSystem.d.ts +4 -0
  36. package/lib/typescript/src/Shared/EIDReader/defaultFileSystem.d.ts.map +1 -1
  37. package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
  38. package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts.map +1 -1
  39. package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts +9 -1
  40. package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/EIDReader/protocol/paceDH.d.ts +56 -0
  42. package/lib/typescript/src/Shared/EIDReader/protocol/paceDH.d.ts.map +1 -0
  43. package/lib/typescript/src/Shared/EIDReader/protocol/paceErrors.d.ts +26 -0
  44. package/lib/typescript/src/Shared/EIDReader/protocol/paceErrors.d.ts.map +1 -0
  45. package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -1
  46. package/lib/typescript/src/Shared/EIDReader/protocol/readBinaryAPDUSender.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -1
  48. package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +9 -1
  50. package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -1
  51. package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts +13 -0
  52. package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts.map +1 -0
  53. package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
  54. package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts.map +1 -1
  55. package/lib/typescript/src/Translation/Resources/en.d.ts +12 -0
  56. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  57. package/lib/typescript/src/Translation/Resources/tr.d.ts +12 -0
  58. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  59. package/lib/typescript/src/version.d.ts +1 -1
  60. package/package.json +1 -1
  61. package/src/Shared/Components/EIDScanner.tsx +153 -69
  62. package/src/Shared/Components/IdentityDocumentCamera.constants.ts +9 -1
  63. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +22 -2
  64. package/src/Shared/Components/IdentityDocumentCamera.tsx +1 -2
  65. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +6 -8
  66. package/src/Shared/EIDReader/cardFileInputStream.ts +30 -6
  67. package/src/Shared/EIDReader/defaultFileSystem.ts +75 -17
  68. package/src/Shared/EIDReader/eidReader.ts +172 -24
  69. package/src/Shared/EIDReader/lds/icao/dg11File.ts +28 -13
  70. package/src/Shared/EIDReader/nfcManagerCardService.ts +91 -13
  71. package/src/Shared/EIDReader/protocol/paceDH.ts +183 -0
  72. package/src/Shared/EIDReader/protocol/paceErrors.ts +41 -0
  73. package/src/Shared/EIDReader/protocol/paceProtocol.ts +72 -6
  74. package/src/Shared/EIDReader/protocol/readBinaryAPDUSender.ts +7 -4
  75. package/src/Shared/Libs/country-display.utils.ts +12 -1
  76. package/src/Shared/Libs/diagnosticReport.ts +32 -1
  77. package/src/Shared/Libs/diagnostics.ts +38 -14
  78. package/src/Shared/Libs/jp2Decode.ts +48 -0
  79. package/src/Shared/Libs/mrz.utils.ts +27 -6
  80. package/src/Shared/Libs/mrzTransliteration.ts +16 -3
  81. package/src/Translation/Resources/en.ts +19 -0
  82. package/src/Translation/Resources/tr.ts +19 -0
  83. package/src/version.ts +1 -1
@@ -0,0 +1,13 @@
1
+ import { Buffer } from 'buffer';
2
+ /**
3
+ * Decode a JPEG2000 buffer to JPEG using the native iOS decoder.
4
+ *
5
+ * Returns the JPEG bytes + mime type on success, or null when native decode is
6
+ * unavailable (Android, or the module isn't linked) or fails — callers should
7
+ * fall back to their existing behaviour (keep raw JP2).
8
+ */
9
+ export declare function decodeJp2ToJpeg(jp2Buffer: Buffer): Promise<{
10
+ base64: string;
11
+ mimeType: string;
12
+ } | null>;
13
+ //# sourceMappingURL=jp2Decode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jp2Decode.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/jp2Decode.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAsBhC;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAetD"}
@@ -1 +1 @@
1
- {"version":3,"file":"mrz.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/mrz.utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA6DpD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;sBAOwB,MAAM,KAAG,MAAM;2BA2oC7B,MAAM,gBACF,OAAO,KACnB,mBAAmB;0CAsKuB,MAAM,KAAG,mBAAmB;wCA9mC9B,MAAM,KAAG,MAAM,EAAE,EAAE;wCAwnCnB,MAAM,KAAG,MAAM;gCAmDvB,MAAM;;;;;+BAz1BP,MAAM,KAAG,OAAO;iCA4Bd,MAAM,KAAG,MAAM;wCA20BR,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAAI;kCA97ChE,MAAM,GAAG,IAAI,GAAG,SAAS,KAC9B,MAAM,GAAG,IAAI;;AA68ChB,wBAWE"}
1
+ {"version":3,"file":"mrz.utils.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/mrz.utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AA4EpD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;;sBAOwB,MAAM,KAAG,MAAM;2BA+oC7B,MAAM,gBACF,OAAO,KACnB,mBAAmB;0CAwKuB,MAAM,KAAG,mBAAmB;wCApnC9B,MAAM,KAAG,MAAM,EAAE,EAAE;wCA8nCnB,MAAM,KAAG,MAAM;gCAmDvB,MAAM;;;;;+BA/1BP,MAAM,KAAG,OAAO;iCA4Bd,MAAM,KAAG,MAAM;wCAi1BR,MAAM,GAAG,IAAI,KAAG,MAAM,GAAG,IAAI;kCAn9ChE,MAAM,GAAG,IAAI,GAAG,SAAS,KAC9B,MAAM,GAAG,IAAI;;AAk+ChB,wBAWE"}
@@ -1 +1 @@
1
- {"version":3,"file":"mrzTransliteration.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/mrzTransliteration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAyFhE,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GACvB,YAAY,CAmCd"}
1
+ {"version":3,"file":"mrzTransliteration.d.ts","sourceRoot":"","sources":["../../../../../src/Shared/Libs/mrzTransliteration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAyFhE,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACvC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,GACvB,YAAY,CAgDd"}
@@ -48,6 +48,11 @@ declare const _default: {
48
48
  'diagnosticReport.noticeBody': string;
49
49
  'diagnosticReport.continue': string;
50
50
  'diagnosticReport.cancel': string;
51
+ 'diagnosticReport.shareFailedTitle': string;
52
+ 'diagnosticReport.shareFailedBody': string;
53
+ 'diagnosticReport.uploadAction': string;
54
+ 'diagnosticReport.uploadSuccess': string;
55
+ 'diagnosticReport.uploadFailed': string;
51
56
  'livenessDetectionScreen.guideHeader': string;
52
57
  'livenessDetectionScreen.guideText': string;
53
58
  'livenessDetectionScreen.guidePoint1': string;
@@ -70,6 +75,13 @@ declare const _default: {
70
75
  'eidScannerScreen.guideHeader': string;
71
76
  'eidScannerScreen.guideText': string;
72
77
  'eidScannerScreen.invalidMRZFields': string;
78
+ 'eidScannerScreen.error': string;
79
+ 'eidScannerScreen.errorCode': string;
80
+ 'eidScannerScreen.nfcGenericError': string;
81
+ 'eidScannerScreen.nfcReadFailed': string;
82
+ 'eidScannerScreen.nfcAuthFailed': string;
83
+ 'eidScannerScreen.nfcTagLost': string;
84
+ 'eidScannerScreen.nfcTimeout': string;
73
85
  'eidScannerScreen.faceImageNotFound': string;
74
86
  'eidScannerScreen.documentCode': string;
75
87
  'eidScannerScreen.nationality': string;
@@ -1 +1 @@
1
- {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAgQE"}
1
+ {"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/en.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAmRE"}
@@ -48,6 +48,11 @@ declare const _default: {
48
48
  'diagnosticReport.noticeBody': string;
49
49
  'diagnosticReport.continue': string;
50
50
  'diagnosticReport.cancel': string;
51
+ 'diagnosticReport.shareFailedTitle': string;
52
+ 'diagnosticReport.shareFailedBody': string;
53
+ 'diagnosticReport.uploadAction': string;
54
+ 'diagnosticReport.uploadSuccess': string;
55
+ 'diagnosticReport.uploadFailed': string;
51
56
  'livenessDetectionScreen.guideHeader': string;
52
57
  'livenessDetectionScreen.guideText': string;
53
58
  'livenessDetectionScreen.guidePoint1': string;
@@ -70,6 +75,13 @@ declare const _default: {
70
75
  'eidScannerScreen.guideHeader': string;
71
76
  'eidScannerScreen.guideText': string;
72
77
  'eidScannerScreen.invalidMRZFields': string;
78
+ 'eidScannerScreen.error': string;
79
+ 'eidScannerScreen.errorCode': string;
80
+ 'eidScannerScreen.nfcGenericError': string;
81
+ 'eidScannerScreen.nfcReadFailed': string;
82
+ 'eidScannerScreen.nfcAuthFailed': string;
83
+ 'eidScannerScreen.nfcTagLost': string;
84
+ 'eidScannerScreen.nfcTimeout': string;
73
85
  'eidScannerScreen.faceImageNotFound': string;
74
86
  'eidScannerScreen.documentCode': string;
75
87
  'eidScannerScreen.nationality': string;
@@ -1 +1 @@
1
- {"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAmQE"}
1
+ {"version":3,"file":"tr.d.ts","sourceRoot":"","sources":["../../../../../src/Translation/Resources/tr.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,wBAsRE"}
@@ -1,2 +1,2 @@
1
- export declare const SDK_VERSION = "1.487.0";
1
+ export declare const SDK_VERSION = "1.489.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustchex/react-native-sdk",
3
- "version": "1.487.0",
3
+ "version": "1.489.0",
4
4
  "description": "Trustchex mobile app react native SDK for android or ios devices",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -11,6 +11,7 @@ import { eidReader } from '../EIDReader/eidReader';
11
11
  import NativeProgressBar from './NativeProgressBar';
12
12
  import type { MRZFields } from '../Types/mrzFields';
13
13
  import { buildDocumentName } from '../Libs/mrzTransliteration';
14
+ import type { DocumentName } from '../Types/documentReadResult';
14
15
  import { normalizeFromMRZInfo } from '../Libs/documentDataNormalizer';
15
16
  import { useTranslation } from 'react-i18next';
16
17
  import AppContext from '../Contexts/AppContext';
@@ -55,13 +56,20 @@ const EIDScanner = ({
55
56
  const [documentMRZInfo, setDocumentMRZInfo] = React.useState<MRZInfo>();
56
57
  const [documentFaceImageMimeType, setDocumentFaceImageMimeType] =
57
58
  React.useState<string>();
58
- const [documentName, setDocumentName] = React.useState<import('../Types/documentReadResult').DocumentName>();
59
+ const [documentName, setDocumentName] = React.useState<DocumentName>();
59
60
  const [progress, setProgress] = React.useState(0);
60
61
  const [progressStage, setProgressStage] = React.useState<string>('');
61
62
 
62
63
  // Animation for pulse indicator
63
64
  const pulseAnim = useRef(new Animated.Value(1)).current;
64
65
 
66
+ // Strip MRZ filler (`<`) and trim; returns '' for empty/filler-only input so
67
+ // the UI can hide fields the chip didn't actually provide.
68
+ const cleanField = useCallback((value?: string | null): string => {
69
+ if (!value) return '';
70
+ return value.replace(/</g, ' ').replace(/\s+/g, ' ').trim();
71
+ }, []);
72
+
65
73
  // Format date from YYMMDD to DD/MM/YYYY (matching Flutter SDK)
66
74
  const formatDate = useCallback((dateStr?: string | null) => {
67
75
  if (!dateStr) return '';
@@ -137,20 +145,30 @@ const EIDScanner = ({
137
145
  const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState<string>();
138
146
  const [attemptNumber, setAttemptNumber] = useState<number>(0);
139
147
 
140
- const getFieldsFromMRZ = useCallback((mrz: MRZInfo) => {
141
- return {
142
- documentCode: mrzUtils.normalizeDocumentCode(mrz.getDocumentCode()),
143
- personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
144
- documentNumber: mrz.getDocumentNumber(),
145
- firstName: mrz.getSecondaryIdentifier(),
146
- lastName: mrz.getPrimaryIdentifier(),
147
- birthDate: mrz.getDateOfBirth(),
148
- expirationDate: mrz.getDateOfExpiry(),
149
- issuingState: mrz.getIssuingState(),
150
- sex: mrz.getGender().getStrCode(),
151
- nationality: mrz.getNationality(),
152
- } satisfies MRZFields;
153
- }, []);
148
+ const getFieldsFromMRZ = useCallback(
149
+ (mrz: MRZInfo, name?: DocumentName | null) => {
150
+ // Prefer the resolved name (DG11 printed name / transliteration) over the
151
+ // raw DG1 split. Some chips — notably Turkish PACE passports — return a DG1
152
+ // name field with no `<<` separator, so getPrimaryIdentifier() holds the
153
+ // FULL name and getSecondaryIdentifier() is empty ("name as surname"). The
154
+ // resolved name corrects that; fall back to the raw split when absent.
155
+ const firstName = name?.displayFirst || mrz.getSecondaryIdentifier();
156
+ const lastName = name?.displayLast || mrz.getPrimaryIdentifier();
157
+ return {
158
+ documentCode: mrzUtils.normalizeDocumentCode(mrz.getDocumentCode()),
159
+ personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
160
+ documentNumber: mrz.getDocumentNumber(),
161
+ firstName,
162
+ lastName,
163
+ birthDate: mrz.getDateOfBirth(),
164
+ expirationDate: mrz.getDateOfExpiry(),
165
+ issuingState: mrz.getIssuingState(),
166
+ sex: mrz.getGender().getStrCode(),
167
+ nationality: mrz.getNationality(),
168
+ } satisfies MRZFields;
169
+ },
170
+ []
171
+ );
154
172
 
155
173
  const readNFC = useCallback(async () => {
156
174
  const startTime = Date.now();
@@ -268,7 +286,7 @@ const EIDScanner = ({
268
286
 
269
287
  if (appContext.skipNfcResultScreen && onScanSuccess) {
270
288
  onScanSuccess(
271
- getFieldsFromMRZ(passportData.mrz),
289
+ getFieldsFromMRZ(passportData.mrz, builtName),
272
290
  passportData.faceImage,
273
291
  passportData.mimeType
274
292
  );
@@ -349,7 +367,42 @@ const EIDScanner = ({
349
367
  ).catch(() => {});
350
368
 
351
369
  console.debug('NFC scan error:', error);
352
- // Ignore error - let user retry
370
+
371
+ // Surface an accurate message instead of failing silently. eidReader
372
+ // attaches `nfcCategory` + `nfcCode`.
373
+ // - User-correctable failures (wrong doc data, card moved/too slow, NFC
374
+ // off) get a specific, actionable message and NO code — there's a clear
375
+ // action to take, a support code would just be noise.
376
+ // - Everything else is an unexpected/system error: show a generic message
377
+ // plus the reportable code (e.g. NFC-READ-6A82) so the user can quote it.
378
+ // - user_cancelled stays silent (the user dismissed the native NFC sheet).
379
+ const tagged = error as
380
+ | { nfcCategory?: string; nfcCode?: string }
381
+ | undefined;
382
+ const category = tagged?.nfcCategory ?? errorType;
383
+
384
+ // category -> actionable message key for the user-mistake cases.
385
+ const ACTIONABLE: Record<string, string> = {
386
+ auth_failed: 'eidScannerScreen.nfcAuthFailed',
387
+ tag_lost: 'eidScannerScreen.nfcTagLost',
388
+ timeout: 'eidScannerScreen.nfcTimeout',
389
+ not_enabled: 'eidScannerScreen.nfcNotEnabled',
390
+ };
391
+
392
+ if (category !== 'user_cancelled') {
393
+ const actionableKey = ACTIONABLE[category];
394
+ if (actionableKey) {
395
+ // Clear user action available — meaningful message, no support code.
396
+ Alert.alert(t('eidScannerScreen.error'), t(actionableKey));
397
+ } else {
398
+ // Unexpected/system error — generic message + reportable support code.
399
+ const errorCode = tagged?.nfcCode ?? 'NFC-READ';
400
+ const message = `${t('eidScannerScreen.nfcGenericError')}\n\n${t(
401
+ 'eidScannerScreen.errorCode'
402
+ )}: ${errorCode}`;
403
+ Alert.alert(t('eidScannerScreen.error'), message);
404
+ }
405
+ }
353
406
  } finally {
354
407
  setIsScanning(false);
355
408
  setProgress(0);
@@ -549,7 +602,11 @@ const EIDScanner = ({
549
602
  <View style={styles.idCardPhotoFrame}>
550
603
  {documentFaceImage &&
551
604
  (documentFaceImageMimeType === 'image/jpeg' ||
552
- documentFaceImageMimeType === 'image/png') ? (
605
+ documentFaceImageMimeType === 'image/png' ||
606
+ // Android's <Image> decodes a raw JP2 data URI; on iOS
607
+ // the chip JP2 is converted to JPEG before this point
608
+ // (native ImageIO), so iOS never reaches here as jp2.
609
+ documentFaceImageMimeType === 'image/jp2') ? (
553
610
  <Image
554
611
  source={{
555
612
  uri: `data:${documentFaceImageMimeType};base64,${documentFaceImage}`,
@@ -567,59 +624,86 @@ const EIDScanner = ({
567
624
  </View>
568
625
  </View>
569
626
 
570
- {/* Right column: all fields as grid */}
627
+ {/* Right column: all fields as grid. Each field renders only
628
+ when the chip actually provided a value — filler-only /
629
+ empty fields are hidden rather than shown blank. */}
571
630
  <View style={styles.idCardFields}>
572
- <IDCardField
573
- label={t('eidScannerScreen.surname')}
574
- value={documentName?.displayLast ?? documentMRZInfo.getPrimaryIdentifier()}
575
- />
576
- <IDCardField
577
- label={t('eidScannerScreen.name')}
578
- value={documentName?.displayFirst ?? documentMRZInfo.getSecondaryIdentifier()}
579
- />
580
- <IDCardField
581
- label={t('eidScannerScreen.documentNumber')}
582
- value={documentMRZInfo.getDocumentNumber()}
583
- />
584
- <IDCardField
585
- label={t('eidScannerScreen.birthDate')}
586
- value={formatDate(documentMRZInfo.getDateOfBirth())}
587
- />
588
- <IDCardField
589
- label={t('eidScannerScreen.expirationDate')}
590
- value={formatDate(documentMRZInfo.getDateOfExpiry())}
591
- />
592
- <IDCardField
593
- label={t('eidScannerScreen.gender')}
594
- value={formatGender(
631
+ {(() => {
632
+ const surname =
633
+ documentName?.displayLast ??
634
+ cleanField(documentMRZInfo.getPrimaryIdentifier());
635
+ const givenNames =
636
+ documentName?.displayFirst ??
637
+ cleanField(documentMRZInfo.getSecondaryIdentifier());
638
+ const docNumber = cleanField(
639
+ documentMRZInfo.getDocumentNumber()
640
+ );
641
+ const birthDate = formatDate(
642
+ documentMRZInfo.getDateOfBirth()
643
+ );
644
+ const expiry = formatDate(
645
+ documentMRZInfo.getDateOfExpiry()
646
+ );
647
+ const gender = formatGender(
595
648
  documentMRZInfo.getGender().getStrCode()
596
- )}
597
- />
598
- <IDCardField
599
- label={t('eidScannerScreen.nationality')}
600
- value={formatNationality(
649
+ );
650
+ const nationality = formatNationality(
601
651
  documentMRZInfo.getNationality()
602
- )}
603
- />
604
- <IDCardField
605
- label={t('eidScannerScreen.issuingState')}
606
- value={formatNationality(
652
+ );
653
+ const issuingState = formatNationality(
607
654
  documentMRZInfo.getIssuingState()
608
- )}
609
- />
610
- {!!(
611
- documentMRZInfo.getPersonalNumber() ||
612
- documentMRZInfo.getOptionalData1()
613
- ) && (
614
- <IDCardField
615
- label={t('eidScannerScreen.personalNumber')}
616
- value={
617
- documentMRZInfo.getPersonalNumber() ||
618
- documentMRZInfo.getOptionalData1() ||
619
- ''
620
- }
621
- />
622
- )}
655
+ );
656
+ const personalNumber = cleanField(
657
+ documentMRZInfo.getPersonalNumber() ||
658
+ documentMRZInfo.getOptionalData1()
659
+ );
660
+
661
+ const fields: Array<{ label: string; value: string }> = [
662
+ {
663
+ label: t('eidScannerScreen.surname'),
664
+ value: surname,
665
+ },
666
+ {
667
+ label: t('eidScannerScreen.name'),
668
+ value: givenNames,
669
+ },
670
+ {
671
+ label: t('eidScannerScreen.documentNumber'),
672
+ value: docNumber,
673
+ },
674
+ {
675
+ label: t('eidScannerScreen.birthDate'),
676
+ value: birthDate,
677
+ },
678
+ {
679
+ label: t('eidScannerScreen.expirationDate'),
680
+ value: expiry,
681
+ },
682
+ { label: t('eidScannerScreen.gender'), value: gender },
683
+ {
684
+ label: t('eidScannerScreen.nationality'),
685
+ value: nationality,
686
+ },
687
+ {
688
+ label: t('eidScannerScreen.issuingState'),
689
+ value: issuingState,
690
+ },
691
+ {
692
+ label: t('eidScannerScreen.personalNumber'),
693
+ value: personalNumber,
694
+ },
695
+ ];
696
+
697
+ return fields
698
+ .filter((f) => f.value && f.value.trim().length > 0)
699
+ .map((f) => (
700
+ <IDCardField
701
+ key={f.label}
702
+ label={f.label}
703
+ value={f.value}
704
+ />
705
+ ));
706
+ })()}
623
707
  </View>
624
708
  </View>
625
709
  </View>
@@ -637,7 +721,7 @@ const EIDScanner = ({
637
721
  documentFaceImageMimeType
638
722
  ) {
639
723
  onScanSuccess(
640
- getFieldsFromMRZ(documentMRZInfo),
724
+ getFieldsFromMRZ(documentMRZInfo, documentName),
641
725
  documentFaceImage,
642
726
  documentFaceImageMimeType
643
727
  );
@@ -724,7 +808,7 @@ const EIDScanner = ({
724
808
  documentFaceImageMimeType
725
809
  ) {
726
810
  onScanSuccess(
727
- getFieldsFromMRZ(documentMRZInfo),
811
+ getFieldsFromMRZ(documentMRZInfo, documentName),
728
812
  documentFaceImage,
729
813
  documentFaceImageMimeType
730
814
  );
@@ -17,7 +17,15 @@ export const REQUIRED_CONSISTENT_DOCTYPE_DETECTIONS = 3;
17
17
  export const SIGNATURE_REGEX = /s[gi]g?n[au]?t[u]?r|imz[a]s?/i;
18
18
  export const SIGNATURE_TEXT_REGEX = /signature|imza|İmza/i;
19
19
  export const MRZ_BLOCK_PATTERN = /[A-Z0-9<]{8,}.*</i;
20
- export const PASSPORT_MRZ_PATTERN = /P<[A-Z]{3}/;
20
+ // Matches the line-1 start of ANY ICAO TD3 passport MRZ, so the camera routes
21
+ // every passport variant into the passport flow (not the ID-card back-side
22
+ // flow). Per ICAO 9303 Part 4: `P` + a type char (filler `<` or a subtype
23
+ // letter — `P<`, `PP` German, `PD`/`PS`/`PO` diplomatic/service/official) +
24
+ // the issuing state (3 letters, OR the German 1-letter `D` padded with filler,
25
+ // e.g. `P<D<<` / `PPD<<`). Previously only `P<[A-Z]{3}` matched, so German `PP`
26
+ // and 1-letter-issuer passports fell through to the ID-card flow and were
27
+ // wrongly asked for a back side.
28
+ export const PASSPORT_MRZ_PATTERN = /P[A-Z<](?:[A-Z]{3}|[A-Z]<<)[A-Z<]*<</;
21
29
 
22
30
  // BDDK Madde 7 - Security Feature Validation
23
31
  // When NFC chip reading unavailable, must verify at least 4 visual security features
@@ -325,13 +325,24 @@ export function handleIDBackFlow(
325
325
  *
326
326
  * PASSPORT: → COMPLETED (no back side)
327
327
  * ID CARD: → SCAN_ID_BACK
328
+ *
329
+ * Routing is PASSPORT-BIASED on purpose: a passport has no back side, so if we
330
+ * route it to SCAN_ID_BACK it deadlocks forever waiting for a back that will
331
+ * never appear. We therefore only go to SCAN_ID_BACK when there is POSITIVE
332
+ * evidence the document is an ID card (a parsed MRZ document code that is
333
+ * present and not 'P'). When the type is ambiguous — e.g. the passport MRZ was
334
+ * never read as 'P' so the front step locked `detectedDocumentType` as
335
+ * 'ID_FRONT' (the torch/tilt during hologram capture makes the MRZ unreadable),
336
+ * or no MRZ code survived at all — we COMPLETE rather than wait for a
337
+ * non-existent back side. A two-sided ID card always yields an 'I'/'A'/'C'
338
+ * (non-'P') MRZ code, so genuine ID cards still route to SCAN_ID_BACK.
328
339
  */
329
340
  export function getNextStepAfterHologram(
330
341
  detectedDocumentType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN',
331
342
  currentFrameDocType: 'ID_FRONT' | 'ID_BACK' | 'PASSPORT' | 'UNKNOWN',
332
343
  mrzDocCode: string | undefined
333
344
  ): 'COMPLETED' | 'SCAN_ID_BACK' {
334
- // Check multiple sources to determine if it's a passport
345
+ // Any positive passport signal done (no back side).
335
346
  const isPassport =
336
347
  detectedDocumentType === 'PASSPORT' ||
337
348
  currentFrameDocType === 'PASSPORT' ||
@@ -341,5 +352,14 @@ export function getNextStepAfterHologram(
341
352
  return 'COMPLETED';
342
353
  }
343
354
 
344
- return 'SCAN_ID_BACK';
355
+ // Only continue to the back side when we have POSITIVE proof it's an ID card:
356
+ // a real MRZ document code that is present and not a passport. Without that
357
+ // proof, default to COMPLETED so a misclassified passport can't hang waiting
358
+ // for a back side that doesn't exist.
359
+ const hasIdCardMrzCode = !!mrzDocCode && mrzDocCode !== 'P';
360
+ if (hasIdCardMrzCode) {
361
+ return 'SCAN_ID_BACK';
362
+ }
363
+
364
+ return 'COMPLETED';
345
365
  }
@@ -2624,8 +2624,7 @@ const IdentityDocumentCamera = ({
2624
2624
  isFrameBlurry,
2625
2625
  allElementsDetected,
2626
2626
  elementsOutsideScanArea,
2627
- t,
2628
- mrzReliable
2627
+ t
2629
2628
  )}
2630
2629
  </AnimatedText>
2631
2630
  </View>
@@ -96,19 +96,17 @@ export function getStatusMessage(
96
96
  isFrameBlurry: boolean,
97
97
  allElementsDetected: boolean,
98
98
  elementsOutsideScanArea: string[],
99
- t: (key: string, params?: Record<string, unknown>) => string,
100
- mrzReliable: boolean = false
99
+ t: (key: string, params?: Record<string, unknown>) => string
101
100
  ): string {
102
101
  if (nextStep === 'COMPLETED') {
103
102
  return t('identityDocumentCamera.scanCompleted');
104
103
  }
105
104
 
106
- // MRZ reliably detected (stable consensus) the green "GO" message. Ranks
107
- // above the in-progress states so the user gets a clear positive signal the
108
- // moment the reading has settled, but never on an early/transient valid read.
109
- if (mrzReliable) {
110
- return t('identityDocumentCamera.mrzDetected');
111
- }
105
+ // NOTE: the "MRZ detected — hold steady" message was intentionally removed
106
+ // (along with its former `mrzReliable` parameter). User-facing guidance
107
+ // should only describe the SCAN STEP (front side, back side, hologram, etc.),
108
+ // not low-level MRZ-detection state. We fall through to the step-specific
109
+ // messages below.
112
110
 
113
111
  if (status === 'INCORRECT') {
114
112
  if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
@@ -4,6 +4,10 @@ import type { FileSystemStructured } from './smartcards/fileSystemStructured';
4
4
  import { Buffer } from 'buffer';
5
5
 
6
6
  export class CardFileInputStream extends InputStream {
7
+ /** Max re-issues of a single block read before failing — mitigates transient
8
+ * iOS NFC transceive drops during large-file reads (e.g. DG2). */
9
+ private static readonly MAX_BLOCK_READ_RETRIES = 5;
10
+
7
11
  private path!: FileInfo[];
8
12
  private buffer!: Buffer;
9
13
  private bufferLength!: number;
@@ -69,12 +73,32 @@ export class CardFileInputStream extends InputStream {
69
73
  newOffsetBufferInFile = this.offsetBufferInFile + this.bufferLength;
70
74
  let newOffsetInBuffer = 0;
71
75
  let newBufferLength = 0;
72
- while (newBufferLength === 0) {
73
- newBufferLength = await this.fillBufferFromFile(
74
- this.path,
75
- newOffsetBufferInFile,
76
- le
77
- );
76
+ // Retry a failed block read. The iOS NFC stack (react-native-nfc-manager)
77
+ // throws transient "READ BINARY" transceive errors during the many
78
+ // block reads of a large file like DG2 (face image); a single drop
79
+ // previously aborted the whole read with "Could not read file". Re-issue
80
+ // the SAME block (offset unchanged) a few times before giving up — this
81
+ // is idempotent (no secure-messaging state advances on a failed read).
82
+ let attempt = 0;
83
+ for (;;) {
84
+ try {
85
+ newBufferLength = 0;
86
+ while (newBufferLength === 0) {
87
+ newBufferLength = await this.fillBufferFromFile(
88
+ this.path,
89
+ newOffsetBufferInFile,
90
+ le
91
+ );
92
+ }
93
+ break;
94
+ } catch (blockError) {
95
+ if (++attempt >= CardFileInputStream.MAX_BLOCK_READ_RETRIES) {
96
+ throw blockError;
97
+ }
98
+ // Brief backoff: an immediate re-issue after a transient NFC drop
99
+ // usually fails again; a short pause lets the RF field re-stabilise.
100
+ await new Promise((r) => setTimeout(r, 80 * attempt));
101
+ }
78
102
  }
79
103
 
80
104
  this.offsetBufferInFile = newOffsetBufferInFile;