@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.
- package/ios/ImageDecoder/ImageDecoderModule.m +12 -0
- package/ios/ImageDecoder/ImageDecoderModule.swift +43 -0
- package/lib/module/Shared/Components/EIDScanner.js +100 -37
- package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +9 -1
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +22 -2
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +1 -1
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +7 -7
- package/lib/module/Shared/EIDReader/cardFileInputStream.js +25 -2
- package/lib/module/Shared/EIDReader/defaultFileSystem.js +56 -6
- package/lib/module/Shared/EIDReader/eidReader.js +138 -16
- package/lib/module/Shared/EIDReader/lds/icao/dg11File.js +23 -6
- package/lib/module/Shared/EIDReader/nfcManagerCardService.js +79 -9
- package/lib/module/Shared/EIDReader/protocol/paceDH.js +101 -0
- package/lib/module/Shared/EIDReader/protocol/paceErrors.js +34 -0
- package/lib/module/Shared/EIDReader/protocol/paceProtocol.js +53 -4
- package/lib/module/Shared/EIDReader/protocol/readBinaryAPDUSender.js +7 -3
- package/lib/module/Shared/Libs/country-display.utils.js +13 -1
- package/lib/module/Shared/Libs/diagnosticReport.js +24 -1
- package/lib/module/Shared/Libs/diagnostics.js +30 -8
- package/lib/module/Shared/Libs/jp2Decode.js +43 -0
- package/lib/module/Shared/Libs/mrz.utils.js +22 -5
- package/lib/module/Shared/Libs/mrzTransliteration.js +14 -1
- package/lib/module/Translation/Resources/en.js +12 -0
- package/lib/module/Translation/Resources/tr.js +12 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +11 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/cardFileInputStream.d.ts +3 -0
- package/lib/typescript/src/Shared/EIDReader/cardFileInputStream.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/defaultFileSystem.d.ts +4 -0
- package/lib/typescript/src/Shared/EIDReader/defaultFileSystem.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts +9 -1
- package/lib/typescript/src/Shared/EIDReader/nfcManagerCardService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/protocol/paceDH.d.ts +56 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceDH.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceErrors.d.ts +26 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceErrors.d.ts.map +1 -0
- package/lib/typescript/src/Shared/EIDReader/protocol/paceProtocol.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/protocol/readBinaryAPDUSender.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/country-display.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +9 -1
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts +13 -0
- package/lib/typescript/src/Shared/Libs/jp2Decode.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +12 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +12 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/Shared/Components/EIDScanner.tsx +153 -69
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +9 -1
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +22 -2
- package/src/Shared/Components/IdentityDocumentCamera.tsx +1 -2
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +6 -8
- package/src/Shared/EIDReader/cardFileInputStream.ts +30 -6
- package/src/Shared/EIDReader/defaultFileSystem.ts +75 -17
- package/src/Shared/EIDReader/eidReader.ts +172 -24
- package/src/Shared/EIDReader/lds/icao/dg11File.ts +28 -13
- package/src/Shared/EIDReader/nfcManagerCardService.ts +91 -13
- package/src/Shared/EIDReader/protocol/paceDH.ts +183 -0
- package/src/Shared/EIDReader/protocol/paceErrors.ts +41 -0
- package/src/Shared/EIDReader/protocol/paceProtocol.ts +72 -6
- package/src/Shared/EIDReader/protocol/readBinaryAPDUSender.ts +7 -4
- package/src/Shared/Libs/country-display.utils.ts +12 -1
- package/src/Shared/Libs/diagnosticReport.ts +32 -1
- package/src/Shared/Libs/diagnostics.ts +38 -14
- package/src/Shared/Libs/jp2Decode.ts +48 -0
- package/src/Shared/Libs/mrz.utils.ts +27 -6
- package/src/Shared/Libs/mrzTransliteration.ts +16 -3
- package/src/Translation/Resources/en.ts +19 -0
- package/src/Translation/Resources/tr.ts +19 -0
- 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;
|
|
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,
|
|
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":"
|
|
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":"
|
|
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.
|
|
1
|
+
export declare const SDK_VERSION = "1.489.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -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<
|
|
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(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
<
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|