@trustchex/react-native-sdk 1.374.0 → 1.409.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/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -21
- package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
- package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
- package/ios/Camera/TrustchexCameraView.swift +9 -20
- package/ios/MLKit/MLKitModule.swift +1 -1
- package/ios/OpenCV/OpenCVHelper.h +0 -7
- package/ios/OpenCV/OpenCVHelper.mm +0 -60
- package/ios/OpenCV/OpenCVModule.h +0 -4
- package/ios/OpenCV/OpenCVModule.mm +440 -358
- package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
- package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
- package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
- package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
- package/lib/module/Screens/Static/ResultScreen.js +27 -13
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
- package/lib/module/Shared/Animations/video-call.json +1 -0
- package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
- package/lib/module/Shared/Components/DebugOverlay.js +541 -0
- package/lib/module/Shared/Components/EIDScanner.js +1 -4
- package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +702 -1703
- package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
- package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
- package/lib/module/Shared/Components/NavigationManager.js +15 -3
- package/lib/module/Shared/Contexts/AppContext.js +1 -0
- package/lib/module/Shared/Libs/SignalingClient.js +128 -0
- package/lib/module/Shared/Libs/analytics.utils.js +4 -0
- package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
- package/lib/module/Shared/Libs/http-client.js +9 -0
- package/lib/module/Shared/Libs/promise.utils.js +16 -2
- package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
- package/lib/module/Shared/Services/DataUploadService.js +294 -0
- package/lib/module/Shared/Services/VideoSessionService.js +156 -0
- package/lib/module/Shared/Services/WebRTCService.js +510 -0
- package/lib/module/Shared/Types/analytics.types.js +2 -0
- package/lib/module/Translation/Resources/en.js +20 -0
- package/lib/module/Translation/Resources/tr.js +20 -0
- package/lib/module/Trustchex.js +10 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +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/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
- package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
- package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
- package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
- package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
- package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
- package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
- package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
- package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
- package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
- package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/version.d.ts +1 -1
- package/package.json +29 -2
- package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
- package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
- package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
- package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
- package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
- package/src/Screens/Static/ResultScreen.tsx +58 -23
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
- package/src/Shared/Animations/video-call.json +1 -0
- package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
- package/src/Shared/Components/DebugOverlay.tsx +656 -0
- package/src/Shared/Components/EIDScanner.tsx +1 -5
- package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
- package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
- package/src/Shared/Components/IdentityDocumentCamera.tsx +1089 -2465
- package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
- package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
- package/src/Shared/Components/NavigationManager.tsx +14 -1
- package/src/Shared/Contexts/AppContext.ts +2 -0
- package/src/Shared/Libs/SignalingClient.ts +189 -0
- package/src/Shared/Libs/analytics.utils.ts +4 -0
- package/src/Shared/Libs/deeplink.utils.ts +12 -1
- package/src/Shared/Libs/http-client.ts +10 -0
- package/src/Shared/Libs/promise.utils.ts +16 -2
- package/src/Shared/Libs/status-bar.utils.ts +19 -0
- package/src/Shared/Services/DataUploadService.ts +395 -0
- package/src/Shared/Services/VideoSessionService.ts +190 -0
- package/src/Shared/Services/WebRTCService.ts +636 -0
- package/src/Shared/Types/analytics.types.ts +2 -0
- package/src/Shared/Types/identificationInfo.ts +5 -1
- package/src/Translation/Resources/en.ts +25 -0
- package/src/Translation/Resources/tr.ts +27 -0
- package/src/Trustchex.tsx +12 -2
- package/src/version.ts +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { Dimensions } from 'react-native';
|
|
4
|
+
import { SIGNATURE_REGEX, PASSPORT_MRZ_PATTERN } from "./IdentityDocumentCamera.constants.js";
|
|
5
|
+
import { debugLog, isDebugEnabled } from "../Libs/debug.utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Frame-to-screen coordinate transform using FILL_CENTER scaling
|
|
9
|
+
*/
|
|
10
|
+
export function getFrameToScreenTransform(frameWidth, frameHeight) {
|
|
11
|
+
const screen = Dimensions.get('window');
|
|
12
|
+
const frameAspect = frameWidth / frameHeight;
|
|
13
|
+
const screenAspect = screen.width / screen.height;
|
|
14
|
+
let scale;
|
|
15
|
+
let offsetX = 0;
|
|
16
|
+
let offsetY = 0;
|
|
17
|
+
if (frameAspect > screenAspect) {
|
|
18
|
+
scale = screen.height / frameHeight;
|
|
19
|
+
offsetX = (frameWidth * scale - screen.width) / 2;
|
|
20
|
+
} else {
|
|
21
|
+
scale = screen.width / frameWidth;
|
|
22
|
+
offsetY = (frameHeight * scale - screen.height) / 2;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
scale,
|
|
26
|
+
offsetX,
|
|
27
|
+
offsetY,
|
|
28
|
+
screen
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Compute scan area bounds in frame coordinates
|
|
34
|
+
*/
|
|
35
|
+
export function getScanAreaBounds(frameWidth, frameHeight) {
|
|
36
|
+
const {
|
|
37
|
+
scale,
|
|
38
|
+
offsetX,
|
|
39
|
+
offsetY,
|
|
40
|
+
screen
|
|
41
|
+
} = getFrameToScreenTransform(frameWidth, frameHeight);
|
|
42
|
+
const scanLeft = (screen.width * 0.05 + offsetX) / scale;
|
|
43
|
+
const scanTop = (screen.height * 0.36 + offsetY) / scale;
|
|
44
|
+
const scanRight = (screen.width * 0.95 + offsetX) / scale;
|
|
45
|
+
const scanBottom = (screen.height * 0.64 + offsetY) / scale;
|
|
46
|
+
const isInsideScan = (x, y, w, h) => x >= scanLeft && y >= scanTop && x + w <= scanRight && y + h <= scanBottom;
|
|
47
|
+
return {
|
|
48
|
+
scanLeft,
|
|
49
|
+
scanTop,
|
|
50
|
+
scanRight,
|
|
51
|
+
scanBottom,
|
|
52
|
+
isInsideScan
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Transform frame-space bounds to screen-space bounds
|
|
58
|
+
*/
|
|
59
|
+
export function transformBoundsToScreen(bounds, scale, offsetX, offsetY) {
|
|
60
|
+
return {
|
|
61
|
+
x: bounds.x * scale - offsetX,
|
|
62
|
+
y: bounds.y * scale - offsetY,
|
|
63
|
+
width: bounds.width * scale,
|
|
64
|
+
height: bounds.height * scale
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Unified status message logic used by both voice guidance and render text.
|
|
70
|
+
* Returns the appropriate i18n key arguments for the current scan state.
|
|
71
|
+
*/
|
|
72
|
+
export function getStatusMessage(nextStep, status, detectedDocumentType, isBrightnessLow, isFrameBlurry, allElementsDetected, elementsOutsideScanArea, t) {
|
|
73
|
+
if (nextStep === 'COMPLETED') {
|
|
74
|
+
return t('identityDocumentCamera.scanCompleted');
|
|
75
|
+
}
|
|
76
|
+
if (status === 'INCORRECT') {
|
|
77
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
78
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
79
|
+
}
|
|
80
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
81
|
+
return t('identityDocumentCamera.alignIDBack');
|
|
82
|
+
}
|
|
83
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
84
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
85
|
+
}
|
|
86
|
+
return t('identityDocumentCamera.alignPhotoSide');
|
|
87
|
+
}
|
|
88
|
+
if (isBrightnessLow) {
|
|
89
|
+
return t('identityDocumentCamera.lowBrightness');
|
|
90
|
+
}
|
|
91
|
+
if (isFrameBlurry) {
|
|
92
|
+
return t('identityDocumentCamera.avoidBlur');
|
|
93
|
+
}
|
|
94
|
+
if (status === 'SCANNING' && allElementsDetected && elementsOutsideScanArea.length === 0) {
|
|
95
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
96
|
+
return t('identityDocumentCamera.idCardBackDetected');
|
|
97
|
+
}
|
|
98
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
99
|
+
return t('identityDocumentCamera.passportDetected');
|
|
100
|
+
}
|
|
101
|
+
if (detectedDocumentType === 'ID_FRONT') {
|
|
102
|
+
return t('identityDocumentCamera.idCardFrontDetected');
|
|
103
|
+
}
|
|
104
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
105
|
+
return t('identityDocumentCamera.alignHologram');
|
|
106
|
+
}
|
|
107
|
+
return t('identityDocumentCamera.readingDocument');
|
|
108
|
+
}
|
|
109
|
+
if (elementsOutsideScanArea.length > 0) {
|
|
110
|
+
return t('identityDocumentCamera.centerDocument');
|
|
111
|
+
}
|
|
112
|
+
if ((status === 'SCANNING' || status === 'SEARCHING') && !allElementsDetected) {
|
|
113
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
114
|
+
return t('identityDocumentCamera.alignIDBack');
|
|
115
|
+
}
|
|
116
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
117
|
+
if (detectedDocumentType === 'PASSPORT') {
|
|
118
|
+
return t('identityDocumentCamera.alignPassport');
|
|
119
|
+
}
|
|
120
|
+
if (detectedDocumentType === 'ID_FRONT') {
|
|
121
|
+
return t('identityDocumentCamera.alignIDFront');
|
|
122
|
+
}
|
|
123
|
+
return t('identityDocumentCamera.alignPhotoSide');
|
|
124
|
+
}
|
|
125
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
126
|
+
return t('identityDocumentCamera.alignHologram');
|
|
127
|
+
}
|
|
128
|
+
return t('identityDocumentCamera.readingDocument');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Default fallback
|
|
132
|
+
if (nextStep === 'SCAN_ID_FRONT_OR_PASSPORT') {
|
|
133
|
+
return status === 'SCANNING' ? t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignPhotoSide');
|
|
134
|
+
}
|
|
135
|
+
if (nextStep === 'SCAN_HOLOGRAM') {
|
|
136
|
+
return t('identityDocumentCamera.alignHologram');
|
|
137
|
+
}
|
|
138
|
+
if (nextStep === 'SCAN_ID_BACK') {
|
|
139
|
+
return status === 'SCANNING' ? t('identityDocumentCamera.readingDocument') : t('identityDocumentCamera.alignIDBackSide');
|
|
140
|
+
}
|
|
141
|
+
return '';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Calculate angle from two points in degrees
|
|
146
|
+
*/
|
|
147
|
+
export function angleBetweenPoints(p1, p2) {
|
|
148
|
+
const dx = p2.x - p1.x;
|
|
149
|
+
const dy = p2.y - p1.y;
|
|
150
|
+
return Math.atan2(dy, dx) * (180 / Math.PI);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Calculate distance between two points
|
|
155
|
+
*/
|
|
156
|
+
export function distanceBetweenPoints(p1, p2) {
|
|
157
|
+
const dx = p2.x - p1.x;
|
|
158
|
+
const dy = p2.y - p1.y;
|
|
159
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Detect document type based on faces, OCR text, and MRZ fields
|
|
164
|
+
*/
|
|
165
|
+
export function detectDocumentType(faces, ocrText, mrzFields, frameWidth, mrzText) {
|
|
166
|
+
// Relaxed signature detection: matches signature/imza variants and OCR errors
|
|
167
|
+
const hasSignatureMatch = SIGNATURE_REGEX.test(ocrText);
|
|
168
|
+
if (isDebugEnabled()) {
|
|
169
|
+
debugLog('IdentityDocumentCamera.utils', '[DocType] Detection', {
|
|
170
|
+
faces: faces.length,
|
|
171
|
+
hasMRZ: !!mrzFields,
|
|
172
|
+
hasMRZText: !!mrzText,
|
|
173
|
+
textLength: ocrText?.length,
|
|
174
|
+
hasSignature: hasSignatureMatch
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ID Back: no face + ID MRZ
|
|
179
|
+
if (faces.length === 0 && mrzFields?.documentCode === 'I') {
|
|
180
|
+
return 'ID_BACK';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Passport: face + passport MRZ
|
|
184
|
+
if (faces.length > 0 && mrzFields?.documentCode === 'P') {
|
|
185
|
+
return 'PASSPORT';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check for PASSPORT MRZ pattern BEFORE classifying as ID_FRONT
|
|
189
|
+
// Both passports and ID cards have face + signature, so we MUST check MRZ first
|
|
190
|
+
// Check BOTH parsed mrzText AND raw ocrText — MRZ parsing can fail while
|
|
191
|
+
// the raw OCR still contains P<TUR/P<USA pattern
|
|
192
|
+
const hasPassportMRZPattern = mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText) || PASSPORT_MRZ_PATTERN.test(ocrText);
|
|
193
|
+
if (hasPassportMRZPattern) {
|
|
194
|
+
// Passport MRZ pattern detected (P<TUR, P<USA, etc.)
|
|
195
|
+
// Even if not fully parsed, this is definitely a passport, not ID card
|
|
196
|
+
return 'PASSPORT';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// ID Front: face detected
|
|
200
|
+
// CRITICAL: Only classify as ID_FRONT when we're confident it's NOT a passport
|
|
201
|
+
// This means we must have either:
|
|
202
|
+
// 1. MRZ code 'I' (definitive ID card), OR
|
|
203
|
+
// 2. Face + signature but NO passport MRZ pattern visible
|
|
204
|
+
if (faces.length > 0 && ocrText?.length >= 5) {
|
|
205
|
+
// Filter to card-sized faces only (min 5% of frame width to exclude tiny background faces)
|
|
206
|
+
const cardSizedFaces = frameWidth ? faces.filter(face => face.bounds.width >= frameWidth * 0.05 && face.bounds.height >= frameWidth * 0.05) : faces;
|
|
207
|
+
if (cardSizedFaces.length > 0) {
|
|
208
|
+
// If we have MRZ code 'I', it's definitely an ID card
|
|
209
|
+
if (mrzFields?.documentCode === 'I') {
|
|
210
|
+
return 'ID_FRONT';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// If signature present and NO passport MRZ pattern, likely ID_FRONT
|
|
214
|
+
// But we can't be 100% sure yet - passport MRZ might appear in next frames
|
|
215
|
+
const hasSignature = hasSignatureMatch;
|
|
216
|
+
if (hasSignature && !hasPassportMRZPattern) {
|
|
217
|
+
return 'ID_FRONT';
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return 'UNKNOWN';
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Determine the document type to set based on current frame analysis
|
|
226
|
+
* Handles correction of misdetections and passport pattern checking
|
|
227
|
+
*/
|
|
228
|
+
export function determineDocumentTypeToSet(documentType, cardSizedFaces, parsedMRZFields, mrzText) {
|
|
229
|
+
// CRITICAL: Passport MRZ always takes precedence over other detections
|
|
230
|
+
const hasPassportMRZ = mrzText && mrzText.length > 20 && PASSPORT_MRZ_PATTERN.test(mrzText) || parsedMRZFields?.documentCode === 'P';
|
|
231
|
+
if (hasPassportMRZ) {
|
|
232
|
+
return 'PASSPORT';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// After passport MRZ check, accept the detected type directly
|
|
236
|
+
if (documentType === 'PASSPORT') return 'PASSPORT';
|
|
237
|
+
if (documentType === 'ID_FRONT') return 'ID_FRONT';
|
|
238
|
+
return 'UNKNOWN';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Compare MRZ field values (ignore raw text variations)
|
|
243
|
+
*/
|
|
244
|
+
export function areMRZFieldsEqual(fields1, fields2) {
|
|
245
|
+
if (!fields1 || !fields2) return false;
|
|
246
|
+
// Compare critical fields that define document identity
|
|
247
|
+
return fields1.documentNumber === fields2.documentNumber && fields1.birthDate === fields2.birthDate && fields1.expirationDate === fields2.expirationDate && fields1.firstName === fields2.firstName && fields1.lastName === fields2.lastName && fields1.issuingState === fields2.issuingState;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if all required MRZ fields are present
|
|
252
|
+
*/
|
|
253
|
+
export function hasRequiredMRZFields(fields) {
|
|
254
|
+
return !!fields?.firstName && !!fields?.lastName && !!fields?.documentNumber && !!fields?.birthDate;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Validate if face position has changed within acceptable tolerance
|
|
259
|
+
* Returns true if position is valid (within tolerance)
|
|
260
|
+
*/
|
|
261
|
+
export function validateFacePosition(currentBounds, referenceBounds, isHologramStep) {
|
|
262
|
+
const xDiff = Math.abs(currentBounds.x - referenceBounds.x);
|
|
263
|
+
const yDiff = Math.abs(currentBounds.y - referenceBounds.y);
|
|
264
|
+
const widthDiff = Math.abs(currentBounds.width - referenceBounds.width);
|
|
265
|
+
const heightDiff = Math.abs(currentBounds.height - referenceBounds.height);
|
|
266
|
+
|
|
267
|
+
// Use looser tolerance during hologram step since flash toggling causes position jitter
|
|
268
|
+
const tolerance = isHologramStep ? 0.5 : 0.2;
|
|
269
|
+
const xTolerance = referenceBounds.width * tolerance;
|
|
270
|
+
const yTolerance = referenceBounds.height * tolerance;
|
|
271
|
+
const sizeTolerance = referenceBounds.width * tolerance;
|
|
272
|
+
return xDiff <= xTolerance && yDiff <= yTolerance && widthDiff <= sizeTolerance && heightDiff <= sizeTolerance;
|
|
273
|
+
}
|
|
@@ -29,7 +29,8 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
29
29
|
CONTRACT_ACCEPTANCE: 'ContractAcceptanceScreen',
|
|
30
30
|
IDENTITY_DOCUMENT_SCAN: 'IdentityDocumentScanningScreen',
|
|
31
31
|
IDENTITY_DOCUMENT_EID_SCAN: 'IdentityDocumentEIDScanningScreen',
|
|
32
|
-
LIVENESS_CHECK: 'LivenessDetectionScreen'
|
|
32
|
+
LIVENESS_CHECK: 'LivenessDetectionScreen',
|
|
33
|
+
VIDEO_CALL: 'VideoCallScreen'
|
|
33
34
|
},
|
|
34
35
|
RESULT: 'ResultScreen'
|
|
35
36
|
};
|
|
@@ -69,6 +70,9 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
69
70
|
if (nextStep.type === 'LIVENESS_CHECK') {
|
|
70
71
|
return routes.DYNAMIC_ROUTES.LIVENESS_CHECK;
|
|
71
72
|
}
|
|
73
|
+
if (nextStep.type === 'VIDEO_CALL') {
|
|
74
|
+
return routes.DYNAMIC_ROUTES.VIDEO_CALL;
|
|
75
|
+
}
|
|
72
76
|
return routes.VERIFICATION_SESSION_CHECK;
|
|
73
77
|
}, [appContext, routes.VERIFICATION_SESSION_CHECK, routes.DYNAMIC_ROUTES.CONTRACT_ACCEPTANCE, routes.DYNAMIC_ROUTES.IDENTITY_DOCUMENT_EID_SCAN, routes.DYNAMIC_ROUTES.IDENTITY_DOCUMENT_SCAN, routes.DYNAMIC_ROUTES.LIVENESS_CHECK, routes.RESULT]);
|
|
74
78
|
const goToNextRoute = useCallback(() => {
|
|
@@ -124,8 +128,15 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
124
128
|
contractIds: [],
|
|
125
129
|
deviceInfo: ''
|
|
126
130
|
},
|
|
127
|
-
locale: appContext.locale || i18n.language
|
|
131
|
+
locale: appContext.locale || i18n.language,
|
|
132
|
+
// Explicitly reset collected data fields
|
|
133
|
+
scannedDocument: undefined,
|
|
134
|
+
livenessDetection: undefined,
|
|
135
|
+
authToken: undefined,
|
|
136
|
+
videoSessionId: undefined
|
|
128
137
|
};
|
|
138
|
+
|
|
139
|
+
// Reset branding to defaults while preserving any custom values
|
|
129
140
|
appContext.branding = {
|
|
130
141
|
logoUrl: appContext.branding?.logoUrl || '',
|
|
131
142
|
primaryColor: appContext.branding?.primaryColor || '#000000',
|
|
@@ -138,6 +149,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
138
149
|
appContext.setIsDemoSession?.(false);
|
|
139
150
|
analyticsService.setDemoSession(false);
|
|
140
151
|
}
|
|
152
|
+
appContext.isTestVideoSession = false;
|
|
141
153
|
navigation.dispatch(CommonActions.reset({
|
|
142
154
|
index: 0,
|
|
143
155
|
routes: [{
|
|
@@ -155,7 +167,7 @@ const NavigationManager = /*#__PURE__*/forwardRef(({
|
|
|
155
167
|
usePreventRemove(true, ({
|
|
156
168
|
data
|
|
157
169
|
}) => {
|
|
158
|
-
if (data
|
|
170
|
+
if (data?.action?.type === 'RESET') {
|
|
159
171
|
navigation.dispatch(data.action);
|
|
160
172
|
}
|
|
161
173
|
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import EventSource from 'react-native-sse';
|
|
4
|
+
export class SignalingClient {
|
|
5
|
+
eventSource = null;
|
|
6
|
+
constructor(baseUrl, sessionId, onMessage, identificationId, onSessionEnded) {
|
|
7
|
+
this.baseUrl = baseUrl;
|
|
8
|
+
this.sessionId = sessionId;
|
|
9
|
+
this.onMessage = onMessage;
|
|
10
|
+
this.identificationId = identificationId;
|
|
11
|
+
this.onSessionEnded = onSessionEnded;
|
|
12
|
+
}
|
|
13
|
+
connect() {
|
|
14
|
+
if (this.eventSource) {
|
|
15
|
+
this.eventSource.close();
|
|
16
|
+
}
|
|
17
|
+
const urlParams = new URLSearchParams();
|
|
18
|
+
if (this.identificationId) {
|
|
19
|
+
urlParams.append('identificationId', this.identificationId);
|
|
20
|
+
}
|
|
21
|
+
const url = `${this.baseUrl}/api/app/mobile/video-sessions/${this.sessionId}/signaling/stream?${urlParams.toString()}`;
|
|
22
|
+
console.log('[SignalingClient] Connecting to SSE:', url);
|
|
23
|
+
this.eventSource = new EventSource(url, {
|
|
24
|
+
pollingInterval: 0 // 0 means default/no polling if SSE is real
|
|
25
|
+
});
|
|
26
|
+
const listener = event => {
|
|
27
|
+
if (event.type === 'open') {
|
|
28
|
+
console.log('[SignalingClient] Connected');
|
|
29
|
+
this.onConnected?.();
|
|
30
|
+
} else if (event.type === 'error') {
|
|
31
|
+
console.error('[SignalingClient] Connection error:', JSON.stringify(event, null, 2));
|
|
32
|
+
this.onDisconnected?.();
|
|
33
|
+
} else if (event.type === 'message') {
|
|
34
|
+
try {
|
|
35
|
+
const data = JSON.parse(event.data || '{}');
|
|
36
|
+
if (['offer', 'answer', 'ice-candidate', 'command'].includes(data.type)) {
|
|
37
|
+
this.onMessage({
|
|
38
|
+
id: data.id,
|
|
39
|
+
type: data.type,
|
|
40
|
+
payload: data.payload,
|
|
41
|
+
createdAt: data.createdAt
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error('[SignalingClient] Error parsing message:', e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const customEventListener = event => {
|
|
50
|
+
try {
|
|
51
|
+
const data = JSON.parse(event.data || '{}');
|
|
52
|
+
const eventType = event.type;
|
|
53
|
+
if (['offer', 'answer', 'ice-candidate', 'command'].includes(eventType)) {
|
|
54
|
+
this.onMessage({
|
|
55
|
+
id: data.id,
|
|
56
|
+
type: eventType,
|
|
57
|
+
payload: data.payload,
|
|
58
|
+
createdAt: data.createdAt
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
} catch (e) {
|
|
62
|
+
console.error('[SignalingClient] Error parsing custom event:', e);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Ping handler - server sends pings to keep connection alive
|
|
67
|
+
const pingListener = event => {
|
|
68
|
+
console.log('[SignalingClient] Received ping');
|
|
69
|
+
// Just acknowledge by doing nothing, server-side heartbeat keeps connection alive
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Session-ended event listener
|
|
73
|
+
const sessionEndedListener = event => {
|
|
74
|
+
try {
|
|
75
|
+
const data = JSON.parse(event.data || '{}');
|
|
76
|
+
console.log('[SignalingClient] Session ended:', data.state);
|
|
77
|
+
if (this.onSessionEnded) {
|
|
78
|
+
this.onSessionEnded(data.state);
|
|
79
|
+
}
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error('[SignalingClient] Error parsing session-ended event:', e);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
this.eventSource.addEventListener('open', listener);
|
|
85
|
+
this.eventSource.addEventListener('message', listener);
|
|
86
|
+
this.eventSource.addEventListener('error', listener);
|
|
87
|
+
this.eventSource.addEventListener('ping', pingListener);
|
|
88
|
+
this.eventSource.addEventListener('offer', customEventListener);
|
|
89
|
+
this.eventSource.addEventListener('answer', customEventListener);
|
|
90
|
+
this.eventSource.addEventListener('ice-candidate', customEventListener);
|
|
91
|
+
this.eventSource.addEventListener('command', customEventListener);
|
|
92
|
+
this.eventSource.addEventListener('session-ended', sessionEndedListener);
|
|
93
|
+
}
|
|
94
|
+
async send(type, payload) {
|
|
95
|
+
const urlParams = new URLSearchParams();
|
|
96
|
+
if (this.identificationId) {
|
|
97
|
+
urlParams.append('identificationId', this.identificationId);
|
|
98
|
+
}
|
|
99
|
+
const url = `${this.baseUrl}/api/app/mobile/video-sessions/${this.sessionId}/signaling?${urlParams.toString()}`;
|
|
100
|
+
try {
|
|
101
|
+
const body = {
|
|
102
|
+
type,
|
|
103
|
+
data: payload
|
|
104
|
+
};
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
'Content-Type': 'application/json'
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(body)
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`Failed to send signaling message: ${response.statusText}`);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('[SignalingClient] Error sending message:', error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
disconnect() {
|
|
121
|
+
if (this.eventSource) {
|
|
122
|
+
this.eventSource.removeAllEventListeners();
|
|
123
|
+
this.eventSource.close();
|
|
124
|
+
this.eventSource = null;
|
|
125
|
+
this.onDisconnected?.();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -188,6 +188,10 @@ const STEP_EVENT_MAP = {
|
|
|
188
188
|
liveness_check: {
|
|
189
189
|
started: AnalyticsEventName.LIVENESS_CHECK_STARTED,
|
|
190
190
|
completed: AnalyticsEventName.LIVENESS_CHECK_COMPLETED
|
|
191
|
+
},
|
|
192
|
+
video_call: {
|
|
193
|
+
started: AnalyticsEventName.VIDEO_CALL_STARTED,
|
|
194
|
+
completed: AnalyticsEventName.VIDEO_CALL_COMPLETED
|
|
191
195
|
}
|
|
192
196
|
};
|
|
193
197
|
function getStepEventName(stepType, suffix) {
|
|
@@ -12,7 +12,7 @@ const handleDeepLink = ({
|
|
|
12
12
|
let baseUrl = '';
|
|
13
13
|
let sessionId = '';
|
|
14
14
|
for (let i = 0; i < segments.length; i++) {
|
|
15
|
-
if (segments[i] === 'verification-session') {
|
|
15
|
+
if (segments[i] === 'verification-session' || segments[i] === 'verification-sessions') {
|
|
16
16
|
sessionId = segments[i + 1] ?? '';
|
|
17
17
|
debugLog('handleDeepLink', 'Found sessionId:', sessionId);
|
|
18
18
|
} else if (segments[i] === 'app-url') {
|
|
@@ -20,6 +20,14 @@ const handleDeepLink = ({
|
|
|
20
20
|
debugLog('handleDeepLink', 'Found baseUrl:', baseUrl);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
// If no app-url segment found, derive baseUrl from the URL itself
|
|
25
|
+
if (!baseUrl && sessionId) {
|
|
26
|
+
const match = url.match(/^(https?:\/\/[^/]+)/);
|
|
27
|
+
if (match) {
|
|
28
|
+
baseUrl = match[1];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
23
31
|
debugLog('handleDeepLink', 'Returning:', {
|
|
24
32
|
baseUrl,
|
|
25
33
|
sessionId
|
|
@@ -47,6 +47,10 @@ const request = async (httpMethod, url, body, simulatedResponse) => {
|
|
|
47
47
|
const startTime = Date.now();
|
|
48
48
|
let statusCode = 0;
|
|
49
49
|
let success = false;
|
|
50
|
+
console.log(`[HTTP] ${httpMethod} ${url}`);
|
|
51
|
+
if (body) {
|
|
52
|
+
console.log('[HTTP] Request body:', JSON.stringify(body).substring(0, 200) + '...');
|
|
53
|
+
}
|
|
50
54
|
try {
|
|
51
55
|
const response = await fetch(url, {
|
|
52
56
|
method: httpMethod,
|
|
@@ -57,11 +61,16 @@ const request = async (httpMethod, url, body, simulatedResponse) => {
|
|
|
57
61
|
});
|
|
58
62
|
statusCode = response.status;
|
|
59
63
|
success = response.ok;
|
|
64
|
+
console.log(`[HTTP] Response status: ${statusCode} ${response.ok ? '✓' : '✗'}`);
|
|
60
65
|
let responseJson = null;
|
|
61
66
|
try {
|
|
62
67
|
responseJson = await response.json();
|
|
68
|
+
if (responseJson) {
|
|
69
|
+
console.log('[HTTP] Response body:', JSON.stringify(responseJson).substring(0, 200) + '...');
|
|
70
|
+
}
|
|
63
71
|
} catch (error) {
|
|
64
72
|
// Invalid JSON response
|
|
73
|
+
console.log('[HTTP] Response body: (non-JSON)');
|
|
65
74
|
}
|
|
66
75
|
|
|
67
76
|
// Track API call performance (non-blocking)
|
|
@@ -2,16 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
const runWithRetry = async (fn, maxRetries = 3, delay = 1000) => {
|
|
4
4
|
let retries = 0;
|
|
5
|
+
let lastError;
|
|
5
6
|
let result;
|
|
6
7
|
while (retries < maxRetries) {
|
|
7
8
|
try {
|
|
9
|
+
if (retries > 0) {
|
|
10
|
+
console.log(`[Retry] Attempt ${retries + 1}/${maxRetries}...`);
|
|
11
|
+
}
|
|
8
12
|
result = await fn();
|
|
13
|
+
if (retries > 0) {
|
|
14
|
+
console.log(`[Retry] ✓ Success on attempt ${retries + 1}`);
|
|
15
|
+
}
|
|
9
16
|
return result;
|
|
10
17
|
} catch (error) {
|
|
18
|
+
lastError = error;
|
|
11
19
|
retries++;
|
|
12
|
-
|
|
20
|
+
console.error(`[Retry] ✗ Attempt ${retries}/${maxRetries} failed:`, error instanceof Error ? error.message : error);
|
|
21
|
+
if (retries < maxRetries) {
|
|
22
|
+
const waitTime = delay * retries;
|
|
23
|
+
console.log(`[Retry] Waiting ${waitTime}ms before retry...`);
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
25
|
+
}
|
|
13
26
|
}
|
|
14
27
|
}
|
|
15
|
-
|
|
28
|
+
console.error('[Retry] ✗ All retries exhausted. Last error:', lastError);
|
|
29
|
+
throw new Error(`Max retries (${maxRetries}) exceeded. Last error: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
|
16
30
|
};
|
|
17
31
|
export { runWithRetry };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { StatusBar } from 'react-native';
|
|
4
|
+
import { useEffect } from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configure status bar for white background with dark content for better contrast
|
|
8
|
+
*/
|
|
9
|
+
export const configureStatusBarForWhiteBackground = () => {
|
|
10
|
+
StatusBar.setBarStyle('dark-content', true);
|
|
11
|
+
StatusBar.setBackgroundColor('#ffffff', true);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook to configure status bar for white background on mount
|
|
16
|
+
*/
|
|
17
|
+
export const useStatusBarWhiteBackground = () => {
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
configureStatusBarForWhiteBackground();
|
|
20
|
+
}, []);
|
|
21
|
+
};
|