@trustchex/react-native-sdk 1.478.7 → 1.483.4
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/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +57 -3
- package/ios/Camera/TrustchexCameraView.swift +20 -15
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +0 -3
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +21 -0
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +1 -1
- package/lib/module/Screens/Static/ResultScreen.js +75 -16
- package/lib/module/Shared/Components/DiagnosticReportButton.js +64 -0
- package/lib/module/Shared/Components/EIDScanner.js +52 -19
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +132 -8
- package/lib/module/Shared/Components/NavigationManager.js +7 -9
- package/lib/module/Shared/Contexts/AppContext.js +3 -0
- package/lib/module/Shared/EIDReader/eidReader.js +167 -30
- package/lib/module/Shared/EIDReader/lds/icao/dg11File.js +77 -0
- package/lib/module/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
- package/lib/module/Shared/Libs/diagnosticReport.js +133 -0
- package/lib/module/Shared/Libs/diagnostics.js +171 -0
- package/lib/module/Shared/Libs/documentDataNormalizer.js +104 -0
- package/lib/module/Shared/Libs/mrz.utils.js +0 -5
- package/lib/module/Shared/Libs/mrzFrameAggregator.js +32 -5
- package/lib/module/Shared/Libs/mrzTransliteration.js +80 -0
- package/lib/module/Shared/Libs/native-device-info.utils.js +67 -0
- package/lib/module/Shared/Libs/sendDiagnosticReport.js +130 -0
- package/lib/module/Shared/Services/DataUploadService.js +6 -3
- package/lib/module/Shared/Types/documentData.js +1 -0
- package/lib/module/Shared/Types/documentReadResult.js +3 -0
- package/lib/module/Translation/Resources/en.js +5 -0
- package/lib/module/Translation/Resources/tr.js +5 -0
- package/lib/module/Trustchex.js +31 -4
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts +20 -0
- package/lib/typescript/src/Shared/Components/DiagnosticReportButton.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts +10 -0
- package/lib/typescript/src/Shared/Components/TrustchexCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +5 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts +2 -0
- package/lib/typescript/src/Shared/EIDReader/eidReader.d.ts.map +1 -1
- package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts +35 -0
- package/lib/typescript/src/Shared/EIDReader/lds/icao/dg11File.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts +48 -0
- package/lib/typescript/src/Shared/Libs/diagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts +145 -0
- package/lib/typescript/src/Shared/Libs/diagnostics.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts +12 -0
- package/lib/typescript/src/Shared/Libs/documentDataNormalizer.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/mrz.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts +33 -0
- package/lib/typescript/src/Shared/Libs/mrzFrameAggregator.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts +3 -0
- package/lib/typescript/src/Shared/Libs/mrzTransliteration.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts +33 -0
- package/lib/typescript/src/Shared/Libs/native-device-info.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts +30 -0
- package/lib/typescript/src/Shared/Libs/sendDiagnosticReport.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +3 -2
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/documentData.d.ts +44 -0
- package/lib/typescript/src/Shared/Types/documentData.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/documentReadResult.d.ts +44 -0
- package/lib/typescript/src/Shared/Types/documentReadResult.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts +1 -1
- package/lib/typescript/src/Shared/Types/mrzFields.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +5 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +5 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +9 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +5 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +0 -4
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +23 -0
- package/src/Screens/Dynamic/VideoCallScreen.tsx +5 -1
- package/src/Screens/Static/ResultScreen.tsx +85 -16
- package/src/Shared/Components/DiagnosticReportButton.tsx +77 -0
- package/src/Shared/Components/EIDScanner.tsx +67 -18
- package/src/Shared/Components/IdentityDocumentCamera.tsx +145 -7
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +6 -0
- package/src/Shared/Components/NavigationManager.tsx +7 -7
- package/src/Shared/Components/TrustchexCamera.tsx +6 -0
- package/src/Shared/Contexts/AppContext.ts +8 -0
- package/src/Shared/EIDReader/eidReader.ts +193 -47
- package/src/Shared/EIDReader/lds/icao/dg11File.ts +94 -0
- package/src/Shared/Libs/MRZ_KNOWN_ISSUES.md +7 -0
- package/src/Shared/Libs/diagnosticReport.ts +206 -0
- package/src/Shared/Libs/diagnostics.ts +251 -0
- package/src/Shared/Libs/documentDataNormalizer.ts +108 -0
- package/src/Shared/Libs/mrz.utils.ts +0 -5
- package/src/Shared/Libs/mrzFrameAggregator.ts +61 -3
- package/src/Shared/Libs/mrzTransliteration.ts +131 -0
- package/src/Shared/Libs/native-device-info.utils.ts +116 -0
- package/src/Shared/Libs/sendDiagnosticReport.ts +165 -0
- package/src/Shared/Services/DataUploadService.ts +9 -3
- package/src/Shared/Types/documentData.ts +52 -0
- package/src/Shared/Types/documentReadResult.ts +47 -0
- package/src/Shared/Types/mrzFields.ts +0 -5
- package/src/Translation/Resources/en.ts +6 -0
- package/src/Translation/Resources/tr.ts +6 -0
- package/src/Trustchex.tsx +48 -3
- package/src/index.tsx +7 -0
- package/src/version.ts +1 -1
|
@@ -40,6 +40,7 @@ import com.google.mlkit.vision.text.TextRecognition
|
|
|
40
40
|
import com.google.mlkit.vision.text.latin.TextRecognizerOptions
|
|
41
41
|
import java.io.ByteArrayOutputStream
|
|
42
42
|
import kotlin.math.max
|
|
43
|
+
import kotlin.math.min
|
|
43
44
|
import java.io.File
|
|
44
45
|
import java.util.concurrent.ExecutorService
|
|
45
46
|
import java.util.concurrent.Executors
|
|
@@ -524,8 +525,35 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
524
525
|
val result = textTask.result
|
|
525
526
|
frameData.putString("resultText", result.text)
|
|
526
527
|
|
|
528
|
+
// Per-symbol confidence aggregation (ML Kit BUNDLED library
|
|
529
|
+
// only — Text.Symbol.confidence is 0 on the unbundled
|
|
530
|
+
// play-services variant). We walk Block→Line→Element→Symbol
|
|
531
|
+
// and accumulate confidence over MRZ-shaped characters
|
|
532
|
+
// ([A-Z0-9<]) only, so surrounding non-MRZ text doesn't skew
|
|
533
|
+
// the weight. The mean and min are surfaced to JS as a
|
|
534
|
+
// per-frame quality signal that down-weights low-confidence
|
|
535
|
+
// frames in the multi-frame vote.
|
|
536
|
+
var confSum = 0f
|
|
537
|
+
var confCount = 0
|
|
538
|
+
var confMin = 1f
|
|
539
|
+
|
|
527
540
|
val blocksArray = Arguments.createArray()
|
|
528
541
|
for (block in result.textBlocks) {
|
|
542
|
+
for (line in block.lines) {
|
|
543
|
+
for (element in line.elements) {
|
|
544
|
+
for (symbol in element.symbols) {
|
|
545
|
+
val ch = symbol.text.firstOrNull() ?: continue
|
|
546
|
+
val upper = ch.uppercaseChar()
|
|
547
|
+
val isMrzChar = upper in 'A'..'Z' ||
|
|
548
|
+
upper in '0'..'9' || upper == '<'
|
|
549
|
+
if (!isMrzChar) continue
|
|
550
|
+
val c = symbol.confidence ?: continue
|
|
551
|
+
confSum += c
|
|
552
|
+
confCount++
|
|
553
|
+
if (c < confMin) confMin = c
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
529
557
|
val blockMap = Arguments.createMap()
|
|
530
558
|
blockMap.putString("text", block.text)
|
|
531
559
|
val bb = block.boundingBox
|
|
@@ -552,6 +580,17 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
552
580
|
blocksArray.pushMap(blockMap)
|
|
553
581
|
}
|
|
554
582
|
frameData.putArray("textBlocks", blocksArray)
|
|
583
|
+
|
|
584
|
+
// Frame-level MRZ-character confidence (mean + min over MRZ
|
|
585
|
+
// symbols). Omitted when ML Kit returned no usable confidence
|
|
586
|
+
// (count == 0) so JS can fall back to band-height weighting.
|
|
587
|
+
if (confCount > 0) {
|
|
588
|
+
val confMap = Arguments.createMap()
|
|
589
|
+
confMap.putDouble("mean", (confSum / confCount).toDouble())
|
|
590
|
+
confMap.putDouble("min", confMin.toDouble())
|
|
591
|
+
confMap.putInt("count", confCount)
|
|
592
|
+
frameData.putMap("textConfidence", confMap)
|
|
593
|
+
}
|
|
555
594
|
} else {
|
|
556
595
|
frameData.putString("resultText", "")
|
|
557
596
|
frameData.putArray("textBlocks", Arguments.createArray())
|
|
@@ -1085,11 +1124,26 @@ class TrustchexCameraView(context: ThemedReactContext) : FrameLayout(context) {
|
|
|
1085
1124
|
full.recycle()
|
|
1086
1125
|
|
|
1087
1126
|
// Upscale so a ~30–44-char MRZ line clears the per-character pixel
|
|
1088
|
-
// minimum.
|
|
1127
|
+
// minimum — but NO FURTHER. ML Kit gains no accuracy above ~24px per
|
|
1128
|
+
// character (Google's guidance), and upscaling past that only adds
|
|
1129
|
+
// interpolation artifacts and latency. So target ~24px/char and take
|
|
1130
|
+
// the SMALLER of (a) the scale needed to reach that target and (b) the
|
|
1131
|
+
// long-edge/latency cap. When the band is already sharp enough, scale
|
|
1132
|
+
// stays at 1 (skip upscaling entirely).
|
|
1089
1133
|
val maxLongEdge = 2200
|
|
1090
1134
|
val longEdge = max(cropW, cropH)
|
|
1091
|
-
val
|
|
1092
|
-
|
|
1135
|
+
val capScale = if (longEdge > 0) {
|
|
1136
|
+
(maxLongEdge.toFloat() / longEdge).coerceIn(1f, 2.5f)
|
|
1137
|
+
} else 1f
|
|
1138
|
+
// Estimate current glyph height: the MRZ band is up to ~3 text lines
|
|
1139
|
+
// (TD1) within the crop; ~1.4 accounts for line spacing vs glyph height.
|
|
1140
|
+
val mrzBandLines = 3f
|
|
1141
|
+
val targetGlyphPx = 24f
|
|
1142
|
+
val estGlyphPx = (cropH / mrzBandLines) / 1.4f
|
|
1143
|
+
val targetScale = if (estGlyphPx > 0f) {
|
|
1144
|
+
(targetGlyphPx / estGlyphPx).coerceAtLeast(1f)
|
|
1145
|
+
} else capScale
|
|
1146
|
+
val scale = min(targetScale, capScale)
|
|
1093
1147
|
val outW = (cropW * scale).toInt()
|
|
1094
1148
|
val outH = (cropH * scale).toInt()
|
|
1095
1149
|
val scaled = if (scale != 1f) {
|
|
@@ -972,19 +972,14 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
972
972
|
blockY = Int(bb.origin.y / roi.scale) + roi.offsetY
|
|
973
973
|
blockWidth = Int(bb.width / roi.scale)
|
|
974
974
|
blockHeight = Int(bb.height / roi.scale)
|
|
975
|
-
} else if isBufferLandscape {
|
|
976
|
-
// Rotate from landscape (1920x1080) to portrait (1080x1920)
|
|
977
|
-
// When rotating 90° clockwise (.right):
|
|
978
|
-
// new_x = old_y
|
|
979
|
-
// new_y = landscape_width - old_x - width
|
|
980
|
-
// new_width = old_height
|
|
981
|
-
// new_height = old_width
|
|
982
|
-
blockX = Int(bb.origin.y)
|
|
983
|
-
blockY = pixelWidth - Int(bb.origin.x) - Int(bb.width)
|
|
984
|
-
blockWidth = Int(bb.height)
|
|
985
|
-
blockHeight = Int(bb.width)
|
|
986
975
|
} else {
|
|
987
|
-
//
|
|
976
|
+
// Fallback when the ROI couldn't be built: OCR ran on
|
|
977
|
+
// `visionImage`, which is created from `orientedImage` — already
|
|
978
|
+
// rotated to portrait (.right applied above when the buffer was
|
|
979
|
+
// landscape) with VisionImage.orientation = .up. ML Kit therefore
|
|
980
|
+
// returns coordinates in PORTRAIT space already, so they are used
|
|
981
|
+
// directly. (A prior landscape→portrait rotation here was a
|
|
982
|
+
// double-rotation bug, since the image was no longer landscape.)
|
|
988
983
|
blockX = Int(bb.origin.x)
|
|
989
984
|
blockY = Int(bb.origin.y)
|
|
990
985
|
blockWidth = Int(bb.width)
|
|
@@ -1091,10 +1086,20 @@ extension TrustchexCameraView: AVCaptureVideoDataOutputSampleBufferDelegate {
|
|
|
1091
1086
|
// Move the crop origin to (0,0) so downstream extents are simple.
|
|
1092
1087
|
img = img.transformed(by: CGAffineTransform(translationX: -cropRect.origin.x, y: -cropRect.origin.y))
|
|
1093
1088
|
|
|
1094
|
-
// Upscale
|
|
1089
|
+
// Upscale for more pixels per character, but only up to ~24px/char — ML Kit
|
|
1090
|
+
// gains no accuracy above that (Google's guidance), and scaling further only
|
|
1091
|
+
// adds interpolation artifacts and latency. Take the SMALLER of the scale
|
|
1092
|
+
// needed to reach the target glyph height and the long-edge/latency cap; a
|
|
1093
|
+
// band that's already sharp enough stays at scale 1 (no upscaling).
|
|
1095
1094
|
let longEdge = max(cropW, cropH)
|
|
1096
|
-
let
|
|
1097
|
-
|
|
1095
|
+
let capScale = longEdge > 0 ? min(max(2200.0 / longEdge, 1.0), 2.5) : 1.0
|
|
1096
|
+
// MRZ band is up to ~3 text lines (TD1) within the crop; ~1.4 accounts for
|
|
1097
|
+
// line spacing vs glyph height.
|
|
1098
|
+
let mrzBandLines: CGFloat = 3.0
|
|
1099
|
+
let targetGlyphPx: CGFloat = 24.0
|
|
1100
|
+
let estGlyphPx = (cropH / mrzBandLines) / 1.4
|
|
1101
|
+
let targetScale = estGlyphPx > 0 ? max(targetGlyphPx / estGlyphPx, 1.0) : capScale
|
|
1102
|
+
let scale = min(targetScale, capScale)
|
|
1098
1103
|
if scale != 1.0 {
|
|
1099
1104
|
img = img.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
|
|
1100
1105
|
}
|
|
@@ -70,9 +70,6 @@ const IdentityDocumentEIDScanningScreen = () => {
|
|
|
70
70
|
setAllowedDocumentTypes(appContext.currentWorkflowStep?.data?.allowedDocumentTypes ?? null);
|
|
71
71
|
setAllowedCountries(appContext.currentWorkflowStep?.data?.allowedCountries ?? null);
|
|
72
72
|
}, [appContext.currentWorkflowStep]);
|
|
73
|
-
if (!appContext.currentWorkflowStep) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
73
|
return /*#__PURE__*/_jsxs(SafeAreaView, {
|
|
77
74
|
style: styles.safeAreaContainer,
|
|
78
75
|
children: [documentNumber && dateOfBirth && dateOfExpiry ? /*#__PURE__*/_jsx(View, {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import React, { useContext, useEffect, useState } from 'react';
|
|
4
|
+
import { buildDocumentName } from "../../Shared/Libs/mrzTransliteration.js";
|
|
5
|
+
import { normalizeFromMRZFields } from "../../Shared/Libs/documentDataNormalizer.js";
|
|
4
6
|
import { Alert, SafeAreaView, StyleSheet, View } from 'react-native';
|
|
5
7
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
6
8
|
import IdentityDocumentCamera from "../../Shared/Components/IdentityDocumentCamera.js";
|
|
@@ -78,6 +80,25 @@ const IdentityDocumentScanningScreen = () => {
|
|
|
78
80
|
|
|
79
81
|
// Track successful document scan completion as funnel step
|
|
80
82
|
trackFunnelStep('Document Scan Completed', 2, 5, 'contract_acceptance', true);
|
|
83
|
+
{
|
|
84
|
+
const documentData = normalizeFromMRZFields(mrzFields);
|
|
85
|
+
const faceImg = idFrontSideData?.faceImage || passportData?.faceImage;
|
|
86
|
+
const builtName = buildDocumentName(documentData.firstName, documentData.lastName, documentData.issuingCountry);
|
|
87
|
+
const documentReadResult = {
|
|
88
|
+
document: {
|
|
89
|
+
...documentData,
|
|
90
|
+
firstName: builtName.displayFirst,
|
|
91
|
+
lastName: builtName.displayLast
|
|
92
|
+
},
|
|
93
|
+
name: builtName,
|
|
94
|
+
face: faceImg ? {
|
|
95
|
+
data: faceImg,
|
|
96
|
+
mimeType: 'image/jpeg'
|
|
97
|
+
} : undefined
|
|
98
|
+
};
|
|
99
|
+
appContext.setLastDocumentRead?.(documentReadResult);
|
|
100
|
+
appContext.onDocumentRead?.(documentReadResult);
|
|
101
|
+
}
|
|
81
102
|
setTimeout(() => navigationManagerRef.current?.navigateToNextStep(), 1000);
|
|
82
103
|
}
|
|
83
104
|
}, [idFrontSideData, idBackSideData, passportData, appContext.identificationInfo, allowedCountries, allowedDocumentTypes, t, appContext]);
|
|
@@ -126,7 +126,7 @@ const VideoCallScreen = ({
|
|
|
126
126
|
try {
|
|
127
127
|
const uploadService = new DataUploadService(baseUrl);
|
|
128
128
|
dataUploadServiceRef.current = uploadService;
|
|
129
|
-
const uploaded = await uploadService.uploadCollectedData(identificationInfo);
|
|
129
|
+
const uploaded = await uploadService.uploadCollectedData(identificationInfo, undefined, appContext.lastDocumentRead?.name);
|
|
130
130
|
if (!uploaded) {
|
|
131
131
|
console.warn('[VideoCallScreen] Upload service returned false');
|
|
132
132
|
if (mounted) {
|
|
@@ -18,7 +18,33 @@ import { Video as VideoCompressor } from 'react-native-compressor';
|
|
|
18
18
|
import { encryptWithAes, getSessionKey } from "../../Shared/Libs/crypto.utils.js";
|
|
19
19
|
import Video from 'react-native-video';
|
|
20
20
|
import StyledButton from "../../Shared/Components/StyledButton.js";
|
|
21
|
+
import DiagnosticReportButton from "../../Shared/Components/DiagnosticReportButton.js";
|
|
21
22
|
import NativeDeviceInfo from "../../Shared/Libs/native-device-info.utils.js";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Collect the captured document/face images (base64) as attachments for a
|
|
26
|
+
* diagnostic report. On an ID card the BACK image is the MRZ side; on a passport
|
|
27
|
+
* the front carries the MRZ. The NFC chip face arrives as `faceImage` with its
|
|
28
|
+
* own mime type, which may be png.
|
|
29
|
+
*/
|
|
30
|
+
const collectDiagnosticImages = doc => {
|
|
31
|
+
if (!doc) return [];
|
|
32
|
+
const out = [];
|
|
33
|
+
const push = (name, b64, mime = 'jpg') => {
|
|
34
|
+
if (b64 && b64 !== '') out.push({
|
|
35
|
+
name,
|
|
36
|
+
base64: b64,
|
|
37
|
+
mime
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
push('mrz-side', doc.backImage);
|
|
41
|
+
push('front', doc.frontImage);
|
|
42
|
+
const faceMime = doc.faceImageMimeType === 'image/png' ? 'png' : 'jpg';
|
|
43
|
+
push('face', doc.faceImage, faceMime);
|
|
44
|
+
push('secondary-face', doc.secondaryFaceImage);
|
|
45
|
+
push('hologram', doc.hologramImage);
|
|
46
|
+
return out;
|
|
47
|
+
};
|
|
22
48
|
import { trackError, trackFunnelStep, useScreenTracking } from "../../Shared/Libs/analytics.utils.js";
|
|
23
49
|
import { analyticsService } from "../../Shared/Services/AnalyticsService.js";
|
|
24
50
|
import { AnalyticsEventName, AnalyticsEventCategory } from "../../Shared/Types/analytics.types.js";
|
|
@@ -152,14 +178,15 @@ const ResultScreen = () => {
|
|
|
152
178
|
if (!scannedDocument.mrzFields) {
|
|
153
179
|
throw new Error('MRZ fields not found');
|
|
154
180
|
}
|
|
155
|
-
|
|
156
|
-
// Backend payload must contain raw MRZ data only.
|
|
157
|
-
// Human-friendly formatting is strictly for UI rendering paths.
|
|
158
181
|
const rawMrzFields = scannedDocument.mrzFields;
|
|
182
|
+
const docName = appContext.lastDocumentRead?.name;
|
|
159
183
|
const identificationDocument = {
|
|
160
184
|
type: rawMrzFields.documentCode,
|
|
161
185
|
name: rawMrzFields.firstName,
|
|
162
186
|
surname: rawMrzFields.lastName,
|
|
187
|
+
displayName: docName?.source === 'dg11' ? docName.displayFirst : undefined,
|
|
188
|
+
displaySurname: docName?.source === 'dg11' ? docName.displayLast : undefined,
|
|
189
|
+
nameSource: docName?.source === 'dg11' ? docName.source : undefined,
|
|
163
190
|
gender: getGenderEnumType(rawMrzFields.sex),
|
|
164
191
|
number: rawMrzFields.documentNumber,
|
|
165
192
|
country: rawMrzFields.issuingState,
|
|
@@ -178,7 +205,7 @@ const ResultScreen = () => {
|
|
|
178
205
|
encryptedData,
|
|
179
206
|
nonce
|
|
180
207
|
});
|
|
181
|
-
}, [apiUrl, getGenderEnumType]);
|
|
208
|
+
}, [apiUrl, appContext.lastDocumentRead, getGenderEnumType]);
|
|
182
209
|
const uploadIdentificationMedia = useCallback(async (identificationId, scannedIdentityDocument, livenessDetection, skipIfAlreadyUploaded) => {
|
|
183
210
|
// Skip media upload if already uploaded during video call
|
|
184
211
|
if (skipIfAlreadyUploaded) {
|
|
@@ -507,18 +534,30 @@ const ResultScreen = () => {
|
|
|
507
534
|
children: [/*#__PURE__*/_jsxs(Text, {
|
|
508
535
|
style: styles.mrzInfoLabel,
|
|
509
536
|
children: [t('eidScannerScreen.name'), ":"]
|
|
510
|
-
}), /*#__PURE__*/
|
|
511
|
-
style: styles.
|
|
512
|
-
children:
|
|
537
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
538
|
+
style: styles.mrzInfoValueGroup,
|
|
539
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
540
|
+
style: styles.mrzInfoText,
|
|
541
|
+
children: appContext.lastDocumentRead?.name.source === 'dg11' ? appContext.lastDocumentRead.name.displayFirst : appContext.identificationInfo.scannedDocument.mrzFields?.firstName
|
|
542
|
+
}), appContext.lastDocumentRead?.name.source === 'dg11' && /*#__PURE__*/_jsx(Text, {
|
|
543
|
+
style: styles.mrzInfoSubText,
|
|
544
|
+
children: appContext.identificationInfo.scannedDocument.mrzFields?.firstName
|
|
545
|
+
})]
|
|
513
546
|
})]
|
|
514
547
|
}), /*#__PURE__*/_jsxs(View, {
|
|
515
548
|
style: styles.mrzInfoItem,
|
|
516
549
|
children: [/*#__PURE__*/_jsxs(Text, {
|
|
517
550
|
style: styles.mrzInfoLabel,
|
|
518
551
|
children: [t('eidScannerScreen.surname'), ":"]
|
|
519
|
-
}), /*#__PURE__*/
|
|
520
|
-
style: styles.
|
|
521
|
-
children:
|
|
552
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
553
|
+
style: styles.mrzInfoValueGroup,
|
|
554
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
555
|
+
style: styles.mrzInfoText,
|
|
556
|
+
children: appContext.lastDocumentRead?.name.source === 'dg11' ? appContext.lastDocumentRead.name.displayLast : appContext.identificationInfo.scannedDocument.mrzFields?.lastName
|
|
557
|
+
}), appContext.lastDocumentRead?.name.source === 'dg11' && /*#__PURE__*/_jsx(Text, {
|
|
558
|
+
style: styles.mrzInfoSubText,
|
|
559
|
+
children: appContext.identificationInfo.scannedDocument.mrzFields?.lastName
|
|
560
|
+
})]
|
|
522
561
|
})]
|
|
523
562
|
}), /*#__PURE__*/_jsxs(View, {
|
|
524
563
|
style: styles.mrzInfoItem,
|
|
@@ -711,18 +750,28 @@ const ResultScreen = () => {
|
|
|
711
750
|
}, `${consentVideo.videoPath}-${index}`))]
|
|
712
751
|
})]
|
|
713
752
|
})
|
|
714
|
-
}), /*#__PURE__*/
|
|
715
|
-
style: {
|
|
716
|
-
paddingBottom: insets.bottom
|
|
717
|
-
},
|
|
718
|
-
children: /*#__PURE__*/_jsx(StyledButton, {
|
|
753
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
754
|
+
style: [styles.footer, {
|
|
755
|
+
paddingBottom: insets.bottom + 20
|
|
756
|
+
}],
|
|
757
|
+
children: [/*#__PURE__*/_jsx(StyledButton, {
|
|
719
758
|
mode: "contained",
|
|
720
759
|
onPress: () => {
|
|
721
760
|
appContext.onCompleted?.();
|
|
722
761
|
navigationManagerRef.current?.reset();
|
|
723
762
|
},
|
|
724
763
|
children: t('resultScreen.demoStartOver')
|
|
725
|
-
})
|
|
764
|
+
}), /*#__PURE__*/_jsx(DiagnosticReportButton, {
|
|
765
|
+
sessionId: appContext.identificationInfo?.sessionId,
|
|
766
|
+
scan: {
|
|
767
|
+
documentType: appContext.identificationInfo?.scannedDocument?.documentType,
|
|
768
|
+
dataSource: appContext.identificationInfo?.scannedDocument?.dataSource,
|
|
769
|
+
mrzText: appContext.identificationInfo?.scannedDocument?.mrzText,
|
|
770
|
+
mrzFields: appContext.identificationInfo?.scannedDocument?.mrzFields,
|
|
771
|
+
barcodeValue: appContext.identificationInfo?.scannedDocument?.barcodeValue
|
|
772
|
+
},
|
|
773
|
+
images: collectDiagnosticImages(appContext.identificationInfo?.scannedDocument)
|
|
774
|
+
})]
|
|
726
775
|
})]
|
|
727
776
|
}) : /*#__PURE__*/_jsxs(_Fragment, {
|
|
728
777
|
children: [/*#__PURE__*/_jsx(LottieView, {
|
|
@@ -846,6 +895,16 @@ const styles = StyleSheet.create({
|
|
|
846
895
|
fontSize: 10,
|
|
847
896
|
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace'
|
|
848
897
|
},
|
|
898
|
+
mrzInfoValueGroup: {
|
|
899
|
+
flexDirection: 'column',
|
|
900
|
+
alignItems: 'flex-end'
|
|
901
|
+
},
|
|
902
|
+
mrzInfoSubText: {
|
|
903
|
+
color: '#aaa',
|
|
904
|
+
fontSize: 9,
|
|
905
|
+
fontFamily: Platform.OS === 'ios' ? 'Courier' : 'monospace',
|
|
906
|
+
marginTop: 1
|
|
907
|
+
},
|
|
849
908
|
mrzTextContainer: {
|
|
850
909
|
padding: 12,
|
|
851
910
|
backgroundColor: '#F3F4F6',
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { Alert } from 'react-native';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import StyledButton from "./StyledButton.js";
|
|
7
|
+
import { sendDiagnosticReport, isDiagnosticSharingAvailable } from "../Libs/sendDiagnosticReport.js";
|
|
8
|
+
import { SDK_VERSION } from "../../version.js";
|
|
9
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
10
|
+
/**
|
|
11
|
+
* "Report a scanning issue" button for the result screen.
|
|
12
|
+
*
|
|
13
|
+
* Shows a brief notice describing what will be shared (the draft itself is the
|
|
14
|
+
* user's review/consent step), then opens the OS share sheet — the user picks
|
|
15
|
+
* email / WhatsApp / Drive / etc. and sends the diagnostic bundle to support.
|
|
16
|
+
* Self-contained so the result screen only needs to render it with the scan
|
|
17
|
+
* data + captured images.
|
|
18
|
+
*/
|
|
19
|
+
const DiagnosticReportButton = ({
|
|
20
|
+
scan,
|
|
21
|
+
sessionId,
|
|
22
|
+
images
|
|
23
|
+
}) => {
|
|
24
|
+
const {
|
|
25
|
+
t
|
|
26
|
+
} = useTranslation();
|
|
27
|
+
const [sending, setSending] = useState(false);
|
|
28
|
+
|
|
29
|
+
// `react-native-share` is optional — hide the button entirely when the host
|
|
30
|
+
// app hasn't installed it, so apps that don't want diagnostics see no UI.
|
|
31
|
+
if (!isDiagnosticSharingAvailable()) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const onPress = () => {
|
|
35
|
+
if (sending) return;
|
|
36
|
+
// Brief notice — the share draft is the actual review/consent step.
|
|
37
|
+
Alert.alert(t('diagnosticReport.noticeTitle'), t('diagnosticReport.noticeBody'), [{
|
|
38
|
+
text: t('diagnosticReport.cancel'),
|
|
39
|
+
style: 'cancel'
|
|
40
|
+
}, {
|
|
41
|
+
text: t('diagnosticReport.continue'),
|
|
42
|
+
onPress: async () => {
|
|
43
|
+
setSending(true);
|
|
44
|
+
try {
|
|
45
|
+
await sendDiagnosticReport({
|
|
46
|
+
scan,
|
|
47
|
+
sdkVersion: SDK_VERSION,
|
|
48
|
+
sessionId,
|
|
49
|
+
images
|
|
50
|
+
});
|
|
51
|
+
} finally {
|
|
52
|
+
setSending(false);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}]);
|
|
56
|
+
};
|
|
57
|
+
return /*#__PURE__*/_jsx(StyledButton, {
|
|
58
|
+
mode: "outlined",
|
|
59
|
+
onPress: onPress,
|
|
60
|
+
disabled: sending,
|
|
61
|
+
children: t('diagnosticReport.button')
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
export default DiagnosticReportButton;
|
|
@@ -9,6 +9,8 @@ import PermissionManager from "../Libs/permissions.utils.js";
|
|
|
9
9
|
import { debugError } from "../Libs/debug.utils.js";
|
|
10
10
|
import { eidReader } from "../EIDReader/eidReader.js";
|
|
11
11
|
import NativeProgressBar from "./NativeProgressBar.js";
|
|
12
|
+
import { buildDocumentName } from "../Libs/mrzTransliteration.js";
|
|
13
|
+
import { normalizeFromMRZInfo } from "../Libs/documentDataNormalizer.js";
|
|
12
14
|
import { useTranslation } from 'react-i18next';
|
|
13
15
|
import AppContext from "../Contexts/AppContext.js";
|
|
14
16
|
import StyledButton from "./StyledButton.js";
|
|
@@ -34,6 +36,7 @@ const EIDScanner = ({
|
|
|
34
36
|
const [documentFaceImage, setDocumentFaceImage] = React.useState();
|
|
35
37
|
const [documentMRZInfo, setDocumentMRZInfo] = React.useState();
|
|
36
38
|
const [documentFaceImageMimeType, setDocumentFaceImageMimeType] = React.useState();
|
|
39
|
+
const [documentName, setDocumentName] = React.useState();
|
|
37
40
|
const [progress, setProgress] = React.useState(0);
|
|
38
41
|
const [progressStage, setProgressStage] = React.useState('');
|
|
39
42
|
|
|
@@ -99,6 +102,20 @@ const EIDScanner = ({
|
|
|
99
102
|
}, [i18n.language]);
|
|
100
103
|
const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState();
|
|
101
104
|
const [attemptNumber, setAttemptNumber] = useState(0);
|
|
105
|
+
const getFieldsFromMRZ = useCallback(mrz => {
|
|
106
|
+
return {
|
|
107
|
+
documentCode: mrz.getDocumentCode(),
|
|
108
|
+
personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
|
|
109
|
+
documentNumber: mrz.getDocumentNumber(),
|
|
110
|
+
firstName: mrz.getSecondaryIdentifier(),
|
|
111
|
+
lastName: mrz.getPrimaryIdentifier(),
|
|
112
|
+
birthDate: mrz.getDateOfBirth(),
|
|
113
|
+
expirationDate: mrz.getDateOfExpiry(),
|
|
114
|
+
issuingState: mrz.getIssuingState(),
|
|
115
|
+
sex: mrz.getGender().getStrCode(),
|
|
116
|
+
nationality: mrz.getNationality()
|
|
117
|
+
};
|
|
118
|
+
}, []);
|
|
102
119
|
const readNFC = useCallback(async () => {
|
|
103
120
|
const startTime = Date.now();
|
|
104
121
|
const currentAttempt = attemptNumber + 1;
|
|
@@ -160,6 +177,25 @@ const EIDScanner = ({
|
|
|
160
177
|
setDocumentMRZInfo(passportData.mrz);
|
|
161
178
|
setDocumentFaceImageMimeType(passportData.mimeType);
|
|
162
179
|
setIsScanned(true);
|
|
180
|
+
{
|
|
181
|
+
const documentData = normalizeFromMRZInfo(passportData.mrz);
|
|
182
|
+
const builtName = buildDocumentName(documentData.firstName, documentData.lastName, documentData.issuingCountry, passportData.dg11FirstName, passportData.dg11LastName);
|
|
183
|
+
setDocumentName(builtName);
|
|
184
|
+
const documentReadResult = {
|
|
185
|
+
document: {
|
|
186
|
+
...documentData,
|
|
187
|
+
firstName: builtName.displayFirst,
|
|
188
|
+
lastName: builtName.displayLast
|
|
189
|
+
},
|
|
190
|
+
name: builtName,
|
|
191
|
+
face: {
|
|
192
|
+
data: passportData.faceImage,
|
|
193
|
+
mimeType: passportData.mimeType
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
appContext.setLastDocumentRead?.(documentReadResult);
|
|
197
|
+
appContext.onDocumentRead?.(documentReadResult);
|
|
198
|
+
}
|
|
163
199
|
await trackEIDScanComplete(docType, scanDuration, currentAttempt).catch(() => {});
|
|
164
200
|
}
|
|
165
201
|
} else {
|
|
@@ -196,21 +232,7 @@ const EIDScanner = ({
|
|
|
196
232
|
setProgressStage('');
|
|
197
233
|
setHasGuideShown(false);
|
|
198
234
|
}
|
|
199
|
-
}, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t]);
|
|
200
|
-
const getFieldsFromMRZ = useCallback(mrz => {
|
|
201
|
-
return {
|
|
202
|
-
documentCode: mrz.getDocumentCode(),
|
|
203
|
-
personalNumber: mrz.getPersonalNumber() || mrz.getOptionalData1(),
|
|
204
|
-
documentNumber: mrz.getDocumentNumber(),
|
|
205
|
-
firstName: mrz.getSecondaryIdentifier(),
|
|
206
|
-
lastName: mrz.getPrimaryIdentifier(),
|
|
207
|
-
birthDate: mrz.getDateOfBirth(),
|
|
208
|
-
expirationDate: mrz.getDateOfExpiry(),
|
|
209
|
-
issuingState: mrz.getIssuingState(),
|
|
210
|
-
sex: mrz.getGender().getStrCode(),
|
|
211
|
-
nationality: mrz.getNationality()
|
|
212
|
-
};
|
|
213
|
-
}, []);
|
|
235
|
+
}, [documentNumber, dateOfBirth, dateOfExpiry, documentType, hasNfc, isEnabled, attemptNumber, t, getFieldsFromMRZ, appContext]);
|
|
214
236
|
useEffect(() => {
|
|
215
237
|
const checkIsSupported = async () => {
|
|
216
238
|
const deviceIsSupported = await NFCManager.isSupported();
|
|
@@ -367,10 +389,12 @@ const EIDScanner = ({
|
|
|
367
389
|
style: styles.idCardFields,
|
|
368
390
|
children: [/*#__PURE__*/_jsx(IDCardField, {
|
|
369
391
|
label: t('eidScannerScreen.surname'),
|
|
370
|
-
value: documentMRZInfo.getPrimaryIdentifier()
|
|
392
|
+
value: documentName?.displayLast ?? documentMRZInfo.getPrimaryIdentifier(),
|
|
393
|
+
subValue: documentName?.source === 'dg11' ? documentMRZInfo.getPrimaryIdentifier() : undefined
|
|
371
394
|
}), /*#__PURE__*/_jsx(IDCardField, {
|
|
372
395
|
label: t('eidScannerScreen.name'),
|
|
373
|
-
value: documentMRZInfo.getSecondaryIdentifier()
|
|
396
|
+
value: documentName?.displayFirst ?? documentMRZInfo.getSecondaryIdentifier(),
|
|
397
|
+
subValue: documentName?.source === 'dg11' ? documentMRZInfo.getSecondaryIdentifier() : undefined
|
|
374
398
|
}), /*#__PURE__*/_jsx(IDCardField, {
|
|
375
399
|
label: t('eidScannerScreen.documentNumber'),
|
|
376
400
|
value: documentMRZInfo.getDocumentNumber()
|
|
@@ -627,6 +651,11 @@ const styles = StyleSheet.create({
|
|
|
627
651
|
flexShrink: 1,
|
|
628
652
|
flexWrap: 'wrap'
|
|
629
653
|
},
|
|
654
|
+
idCardFieldSubValue: {
|
|
655
|
+
fontSize: 9,
|
|
656
|
+
color: '#aaa',
|
|
657
|
+
marginTop: 1
|
|
658
|
+
},
|
|
630
659
|
guide: {
|
|
631
660
|
flex: 1,
|
|
632
661
|
display: 'flex',
|
|
@@ -658,7 +687,8 @@ const styles = StyleSheet.create({
|
|
|
658
687
|
});
|
|
659
688
|
const IDCardField = ({
|
|
660
689
|
label,
|
|
661
|
-
value
|
|
690
|
+
value,
|
|
691
|
+
subValue
|
|
662
692
|
}) => /*#__PURE__*/_jsxs(View, {
|
|
663
693
|
style: styles.idCardFieldItem,
|
|
664
694
|
children: [/*#__PURE__*/_jsx(Text, {
|
|
@@ -668,6 +698,9 @@ const IDCardField = ({
|
|
|
668
698
|
}), /*#__PURE__*/_jsx(Text, {
|
|
669
699
|
style: styles.idCardFieldValue,
|
|
670
700
|
children: value || '—'
|
|
671
|
-
})
|
|
701
|
+
}), subValue ? /*#__PURE__*/_jsx(Text, {
|
|
702
|
+
style: styles.idCardFieldSubValue,
|
|
703
|
+
children: subValue
|
|
704
|
+
}) : null]
|
|
672
705
|
});
|
|
673
706
|
export default EIDScanner;
|