@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.
Files changed (137) hide show
  1. package/android/src/main/java/com/trustchex/reactnativesdk/camera/TrustchexCameraView.kt +1 -21
  2. package/android/src/main/java/com/trustchex/reactnativesdk/mlkit/MLKitModule.kt +1 -1
  3. package/android/src/main/java/com/trustchex/reactnativesdk/opencv/OpenCVModule.kt +636 -301
  4. package/ios/Camera/TrustchexCameraView.swift +9 -20
  5. package/ios/MLKit/MLKitModule.swift +1 -1
  6. package/ios/OpenCV/OpenCVHelper.h +0 -7
  7. package/ios/OpenCV/OpenCVHelper.mm +0 -60
  8. package/ios/OpenCV/OpenCVModule.h +0 -4
  9. package/ios/OpenCV/OpenCVModule.mm +440 -358
  10. package/lib/module/Screens/Debug/BarcodeTestScreen.js +308 -0
  11. package/lib/module/Screens/Debug/MRZTestScreen.js +105 -13
  12. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +49 -29
  13. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +5 -0
  14. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +5 -0
  15. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +26 -6
  16. package/lib/module/Screens/Dynamic/VideoCallScreen.js +676 -0
  17. package/lib/module/Screens/Static/OTPVerificationScreen.js +6 -0
  18. package/lib/module/Screens/Static/QrCodeScanningScreen.js +7 -1
  19. package/lib/module/Screens/Static/ResultScreen.js +27 -13
  20. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +51 -51
  21. package/lib/module/Shared/Animations/video-call.json +1 -0
  22. package/lib/module/Shared/Components/DebugNavigationPanel.js +180 -14
  23. package/lib/module/Shared/Components/DebugOverlay.js +541 -0
  24. package/lib/module/Shared/Components/EIDScanner.js +1 -4
  25. package/lib/module/Shared/Components/IdentityDocumentCamera.constants.js +44 -0
  26. package/lib/module/Shared/Components/IdentityDocumentCamera.flows.js +270 -0
  27. package/lib/module/Shared/Components/IdentityDocumentCamera.js +702 -1703
  28. package/lib/module/Shared/Components/IdentityDocumentCamera.types.js +3 -0
  29. package/lib/module/Shared/Components/IdentityDocumentCamera.utils.js +273 -0
  30. package/lib/module/Shared/Components/NavigationManager.js +15 -3
  31. package/lib/module/Shared/Contexts/AppContext.js +1 -0
  32. package/lib/module/Shared/Libs/SignalingClient.js +128 -0
  33. package/lib/module/Shared/Libs/analytics.utils.js +4 -0
  34. package/lib/module/Shared/Libs/deeplink.utils.js +9 -1
  35. package/lib/module/Shared/Libs/http-client.js +9 -0
  36. package/lib/module/Shared/Libs/promise.utils.js +16 -2
  37. package/lib/module/Shared/Libs/status-bar.utils.js +21 -0
  38. package/lib/module/Shared/Services/DataUploadService.js +294 -0
  39. package/lib/module/Shared/Services/VideoSessionService.js +156 -0
  40. package/lib/module/Shared/Services/WebRTCService.js +510 -0
  41. package/lib/module/Shared/Types/analytics.types.js +2 -0
  42. package/lib/module/Translation/Resources/en.js +20 -0
  43. package/lib/module/Translation/Resources/tr.js +20 -0
  44. package/lib/module/Trustchex.js +10 -0
  45. package/lib/module/version.js +1 -1
  46. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts +3 -0
  47. package/lib/typescript/src/Screens/Debug/BarcodeTestScreen.d.ts.map +1 -0
  48. package/lib/typescript/src/Screens/Debug/MRZTestScreen.d.ts.map +1 -1
  49. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  50. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  51. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  52. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  53. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts +3 -0
  54. package/lib/typescript/src/Screens/Dynamic/VideoCallScreen.d.ts.map +1 -0
  55. package/lib/typescript/src/Screens/Static/OTPVerificationScreen.d.ts.map +1 -1
  56. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  57. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  58. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Components/DebugNavigationPanel.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts +30 -0
  61. package/lib/typescript/src/Shared/Components/DebugOverlay.d.ts.map +1 -0
  62. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  63. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts +35 -0
  64. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.constants.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts +3 -56
  66. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  67. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts +88 -0
  68. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.flows.d.ts.map +1 -0
  69. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts +116 -0
  70. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.types.d.ts.map +1 -0
  71. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts +93 -0
  72. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.utils.d.ts.map +1 -0
  73. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  74. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +1 -0
  75. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  76. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts +24 -0
  77. package/lib/typescript/src/Shared/Libs/SignalingClient.d.ts.map +1 -0
  78. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -1
  79. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  80. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  81. package/lib/typescript/src/Shared/Libs/promise.utils.d.ts.map +1 -1
  82. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts +9 -0
  83. package/lib/typescript/src/Shared/Libs/status-bar.utils.d.ts.map +1 -0
  84. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts +25 -0
  85. package/lib/typescript/src/Shared/Services/DataUploadService.d.ts.map +1 -0
  86. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts +33 -0
  87. package/lib/typescript/src/Shared/Services/VideoSessionService.d.ts.map +1 -0
  88. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts +58 -0
  89. package/lib/typescript/src/Shared/Services/WebRTCService.d.ts.map +1 -0
  90. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +2 -0
  91. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -1
  92. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts +4 -1
  93. package/lib/typescript/src/Shared/Types/identificationInfo.d.ts.map +1 -1
  94. package/lib/typescript/src/Translation/Resources/en.d.ts +20 -0
  95. package/lib/typescript/src/Translation/Resources/en.d.ts.map +1 -1
  96. package/lib/typescript/src/Translation/Resources/tr.d.ts +20 -0
  97. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  98. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  99. package/lib/typescript/src/version.d.ts +1 -1
  100. package/package.json +29 -2
  101. package/src/Screens/Debug/BarcodeTestScreen.tsx +317 -0
  102. package/src/Screens/Debug/MRZTestScreen.tsx +107 -13
  103. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +59 -33
  104. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +6 -0
  105. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +6 -0
  106. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +34 -6
  107. package/src/Screens/Dynamic/VideoCallScreen.tsx +764 -0
  108. package/src/Screens/Static/OTPVerificationScreen.tsx +6 -0
  109. package/src/Screens/Static/QrCodeScanningScreen.tsx +7 -1
  110. package/src/Screens/Static/ResultScreen.tsx +58 -23
  111. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +58 -72
  112. package/src/Shared/Animations/video-call.json +1 -0
  113. package/src/Shared/Components/DebugNavigationPanel.tsx +185 -9
  114. package/src/Shared/Components/DebugOverlay.tsx +656 -0
  115. package/src/Shared/Components/EIDScanner.tsx +1 -5
  116. package/src/Shared/Components/IdentityDocumentCamera.constants.ts +44 -0
  117. package/src/Shared/Components/IdentityDocumentCamera.flows.ts +342 -0
  118. package/src/Shared/Components/IdentityDocumentCamera.tsx +1089 -2465
  119. package/src/Shared/Components/IdentityDocumentCamera.types.ts +136 -0
  120. package/src/Shared/Components/IdentityDocumentCamera.utils.ts +364 -0
  121. package/src/Shared/Components/NavigationManager.tsx +14 -1
  122. package/src/Shared/Contexts/AppContext.ts +2 -0
  123. package/src/Shared/Libs/SignalingClient.ts +189 -0
  124. package/src/Shared/Libs/analytics.utils.ts +4 -0
  125. package/src/Shared/Libs/deeplink.utils.ts +12 -1
  126. package/src/Shared/Libs/http-client.ts +10 -0
  127. package/src/Shared/Libs/promise.utils.ts +16 -2
  128. package/src/Shared/Libs/status-bar.utils.ts +19 -0
  129. package/src/Shared/Services/DataUploadService.ts +395 -0
  130. package/src/Shared/Services/VideoSessionService.ts +190 -0
  131. package/src/Shared/Services/WebRTCService.ts +636 -0
  132. package/src/Shared/Types/analytics.types.ts +2 -0
  133. package/src/Shared/Types/identificationInfo.ts +5 -1
  134. package/src/Translation/Resources/en.ts +25 -0
  135. package/src/Translation/Resources/tr.ts +27 -0
  136. package/src/Trustchex.tsx +12 -2
  137. package/src/version.ts +1 -1
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export {};
@@ -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.action.type === 'RESET') {
170
+ if (data?.action?.type === 'RESET') {
159
171
  navigation.dispatch(data.action);
160
172
  }
161
173
  });
@@ -23,6 +23,7 @@ export default /*#__PURE__*/createContext({
23
23
  workflowSteps: [],
24
24
  currentWorkflowStep: undefined,
25
25
  isDebugNavigated: false,
26
+ isTestVideoSession: false,
26
27
  onCompleted: undefined,
27
28
  onError: undefined,
28
29
  setSessionId: undefined,
@@ -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
- await new Promise(resolve => setTimeout(resolve, delay * retries));
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
- throw new Error('Max retries exceeded');
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
+ };