@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,395 @@
1
+ import RNFS from 'react-native-fs';
2
+ import { Platform } from 'react-native';
3
+ import type {
4
+ IdentificationInfo,
5
+ ScannedIdentityDocument,
6
+ LivenessDetection,
7
+ } from '../Types/identificationInfo';
8
+ import { getSessionKey, encryptWithAes } from '../Libs/crypto.utils';
9
+ import mrzUtils from '../Libs/mrz.utils';
10
+ import httpClient from '../Libs/http-client';
11
+ import { NotFoundError } from '../Libs/http-client';
12
+ import { runWithRetry } from '../Libs/promise.utils';
13
+
14
+ /**
15
+ * Service to upload collected identification data to the backend.
16
+ * This is used to send data before/during video call so agents can see it.
17
+ */
18
+ export class DataUploadService {
19
+ private baseUrl: string;
20
+ private apiUrl: string;
21
+
22
+ constructor(baseUrl: string) {
23
+ this.baseUrl = baseUrl;
24
+ this.apiUrl = `${baseUrl}/api/app/mobile`;
25
+ }
26
+
27
+ private async ensureIdentificationExists(
28
+ identificationId: string
29
+ ): Promise<void> {
30
+ console.log(
31
+ '[DataUploadService] POST',
32
+ `${this.apiUrl}/identifications/${identificationId}`
33
+ );
34
+ await httpClient.post(
35
+ `${this.apiUrl}/identifications/${identificationId}`,
36
+ {}
37
+ );
38
+ console.log('[DataUploadService] ✓ Identification created/verified');
39
+ }
40
+
41
+ /**
42
+ * Submit document data to the backend (same approach as ResultScreen)
43
+ */
44
+ async submitDocumentData(
45
+ identificationId: string,
46
+ scannedDocument: ScannedIdentityDocument,
47
+ sessionKey: string
48
+ ): Promise<void> {
49
+ if (!scannedDocument || scannedDocument.documentType === 'UNKNOWN') {
50
+ console.log('[DataUploadService] No document data to submit');
51
+ return;
52
+ }
53
+
54
+ const mrzFields = scannedDocument.mrzFields;
55
+ if (!mrzFields) {
56
+ console.log('[DataUploadService] No MRZ fields to submit');
57
+ return;
58
+ }
59
+
60
+ const identificationDocument = {
61
+ type: mrzFields.documentCode,
62
+ name: mrzFields.firstName,
63
+ surname: mrzFields.lastName,
64
+ gender: this.getGenderEnumType(mrzFields.sex),
65
+ number: mrzFields.documentNumber,
66
+ country: mrzFields.issuingState,
67
+ barcodeValue: scannedDocument.barcodeValue,
68
+ personalNumber: mrzFields.personalNumber || mrzFields.optional1,
69
+ birthDate: mrzUtils.convertMRZDateToISODate(mrzFields.birthDate),
70
+ expiryDate: mrzUtils.convertMRZDateToISODate(mrzFields.expirationDate),
71
+ dataSource: scannedDocument.dataSource,
72
+ mrzText: scannedDocument.mrzText,
73
+ };
74
+
75
+ console.log(
76
+ '[DataUploadService] Submitting document data:',
77
+ identificationDocument.type,
78
+ identificationDocument.number
79
+ );
80
+
81
+ const { encryptedData, nonce } = encryptWithAes(
82
+ JSON.stringify(identificationDocument),
83
+ sessionKey
84
+ );
85
+
86
+ console.log(
87
+ '[DataUploadService] POST',
88
+ `${this.apiUrl}/identifications/${identificationId}/documents`
89
+ );
90
+ await runWithRetry(() =>
91
+ httpClient.post(
92
+ `${this.apiUrl}/identifications/${identificationId}/documents`,
93
+ {
94
+ encryptedData,
95
+ nonce,
96
+ }
97
+ )
98
+ );
99
+
100
+ console.log('[DataUploadService] ✓ Document data submitted');
101
+ }
102
+
103
+ /**
104
+ * Upload media files (document images, selfies, etc.)
105
+ */
106
+ async uploadMedia(
107
+ identificationId: string,
108
+ scannedDocument?: ScannedIdentityDocument,
109
+ livenessDetection?: LivenessDetection,
110
+ onProgress?: (progress: number) => void
111
+ ): Promise<void> {
112
+ const uploadFileOptions: RNFS.UploadFileOptions = {
113
+ toUrl: `${this.apiUrl}/identifications/${identificationId}/media`,
114
+ method: 'POST',
115
+ headers: {
116
+ Accept: 'application/json',
117
+ },
118
+ files: [],
119
+ progress: (res) => {
120
+ const progress = res.totalBytesSent / res.totalBytesExpectedToSend;
121
+ onProgress?.(progress);
122
+ },
123
+ };
124
+
125
+ // Add document front image
126
+ const frontImage = scannedDocument?.frontImage;
127
+ if (frontImage && frontImage !== '') {
128
+ const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_FRONT_IMAGE.jpg`;
129
+ await RNFS.writeFile(decodeURIComponent(filePath), frontImage, 'base64');
130
+ uploadFileOptions.files.push({
131
+ name: 'files',
132
+ filename: 'DOCUMENT_FRONT_IMAGE.jpg',
133
+ filepath: decodeURIComponent(filePath),
134
+ filetype: 'image/jpeg',
135
+ });
136
+ }
137
+
138
+ // Add document back image
139
+ const backImage = scannedDocument?.backImage;
140
+ if (backImage && backImage !== '') {
141
+ const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_BACK_IMAGE.jpg`;
142
+ await RNFS.writeFile(decodeURIComponent(filePath), backImage, 'base64');
143
+ uploadFileOptions.files.push({
144
+ name: 'files',
145
+ filename: 'DOCUMENT_BACK_IMAGE.jpg',
146
+ filepath: decodeURIComponent(filePath),
147
+ filetype: 'image/jpeg',
148
+ });
149
+ }
150
+
151
+ // Add face image from document
152
+ const faceImage = scannedDocument?.faceImage;
153
+ if (faceImage && faceImage !== '') {
154
+ const filePath = `${RNFS.TemporaryDirectoryPath}/FACE_IMAGE.jpg`;
155
+ await RNFS.writeFile(decodeURIComponent(filePath), faceImage, 'base64');
156
+ uploadFileOptions.files.push({
157
+ name: 'files',
158
+ filename: 'FACE_IMAGE.jpg',
159
+ filepath: decodeURIComponent(filePath),
160
+ filetype: 'image/jpeg',
161
+ });
162
+ }
163
+
164
+ // Add secondary face image from document (optional)
165
+ const secondaryFaceImage = scannedDocument?.secondaryFaceImage;
166
+ if (secondaryFaceImage && secondaryFaceImage !== '') {
167
+ const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_SECONDARY_FACE_IMAGE.jpg`;
168
+ await RNFS.writeFile(
169
+ decodeURIComponent(filePath),
170
+ secondaryFaceImage,
171
+ 'base64'
172
+ );
173
+ uploadFileOptions.files.push({
174
+ name: 'files',
175
+ filename: 'DOCUMENT_SECONDARY_FACE_IMAGE.jpg',
176
+ filepath: decodeURIComponent(filePath),
177
+ filetype: 'image/jpeg',
178
+ });
179
+ }
180
+
181
+ // Add hologram image from document (optional)
182
+ const hologramImage = scannedDocument?.hologramImage;
183
+ if (hologramImage && hologramImage !== '') {
184
+ const filePath = `${RNFS.TemporaryDirectoryPath}/DOCUMENT_HOLOGRAM_IMAGE.jpg`;
185
+ await RNFS.writeFile(
186
+ decodeURIComponent(filePath),
187
+ hologramImage,
188
+ 'base64'
189
+ );
190
+ uploadFileOptions.files.push({
191
+ name: 'files',
192
+ filename: 'DOCUMENT_HOLOGRAM_IMAGE.jpg',
193
+ filepath: decodeURIComponent(filePath),
194
+ filetype: 'image/jpeg',
195
+ });
196
+ }
197
+
198
+ // Add liveness images and selfie from liveness detection
199
+ if (livenessDetection?.instructions) {
200
+ for (const instruction of livenessDetection.instructions) {
201
+ if (instruction?.photo) {
202
+ const filePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_${instruction.instruction}_IMAGE.jpg`;
203
+ await RNFS.writeFile(
204
+ decodeURIComponent(filePath),
205
+ instruction.photo,
206
+ 'base64'
207
+ );
208
+ uploadFileOptions.files.push({
209
+ name: 'files',
210
+ filename: `LIVENESS_${instruction.instruction}_IMAGE.jpg`,
211
+ filepath: decodeURIComponent(filePath),
212
+ filetype: 'image/jpeg',
213
+ });
214
+
215
+ if (instruction.instruction === 'LOOK_STRAIGHT_AND_BLINK') {
216
+ uploadFileOptions.files.push({
217
+ name: 'files',
218
+ filename: 'SELFIE_IMAGE.jpg',
219
+ filepath: decodeURIComponent(filePath),
220
+ filetype: 'image/jpeg',
221
+ });
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ // Add liveness video (optional)
228
+ if (livenessDetection?.videoPath) {
229
+ let videoFilePath: string;
230
+ if (Platform.OS === 'ios') {
231
+ const tempDir = `${RNFS.TemporaryDirectoryPath}/${new Date().getTime()}`;
232
+ await RNFS.mkdir(tempDir);
233
+ videoFilePath = `${tempDir}/LIVENESS_VIDEO.mp4`;
234
+ } else {
235
+ videoFilePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_VIDEO.mp4`;
236
+ }
237
+
238
+ await RNFS.copyFile(livenessDetection.videoPath, videoFilePath);
239
+
240
+ uploadFileOptions.files.push({
241
+ name: 'files',
242
+ filename: 'LIVENESS_VIDEO.mp4',
243
+ filepath: decodeURIComponent(videoFilePath),
244
+ filetype: 'video/mp4',
245
+ });
246
+ }
247
+
248
+ // Skip upload if no files
249
+ if (uploadFileOptions.files.length === 0) {
250
+ console.log('[DataUploadService] No media files to upload');
251
+ return;
252
+ }
253
+
254
+ console.log(
255
+ '[DataUploadService] Uploading',
256
+ uploadFileOptions.files.length,
257
+ 'media files to',
258
+ uploadFileOptions.toUrl
259
+ );
260
+ const response = await runWithRetry(
261
+ () => RNFS.uploadFiles(uploadFileOptions).promise
262
+ );
263
+ console.log(
264
+ '[DataUploadService] Upload response status:',
265
+ response.statusCode
266
+ );
267
+
268
+ if (![200, 201, 204].includes(response.statusCode)) {
269
+ console.error(
270
+ '[DataUploadService] Media upload failed:',
271
+ response.statusCode,
272
+ response.body
273
+ );
274
+ throw new Error(`Media upload failed: ${response.statusCode}`);
275
+ }
276
+
277
+ console.log('[DataUploadService] ✓ Media uploaded successfully');
278
+ }
279
+
280
+ /**
281
+ * Upload all collected data (document + media) before video call
282
+ */
283
+ async uploadCollectedData(
284
+ identificationInfo: IdentificationInfo,
285
+ onProgress?: (progress: number) => void
286
+ ): Promise<boolean> {
287
+ const { identificationId, sessionId, scannedDocument, livenessDetection } =
288
+ identificationInfo;
289
+ let { authToken } = identificationInfo;
290
+
291
+ if (!identificationId) {
292
+ console.log('[DataUploadService] No identification ID, skipping upload');
293
+ return false;
294
+ }
295
+
296
+ console.log(
297
+ '[DataUploadService] ========== UPLOADING COLLECTED DATA =========='
298
+ );
299
+ console.log('[DataUploadService] Identification ID:', identificationId);
300
+ console.log('[DataUploadService] Has document:', !!scannedDocument);
301
+ console.log('[DataUploadService] Has liveness:', !!livenessDetection);
302
+ console.log('[DataUploadService] Has auth token:', !!authToken);
303
+
304
+ try {
305
+ await runWithRetry(() =>
306
+ this.ensureIdentificationExists(identificationId)
307
+ );
308
+
309
+ // Always refresh session key for current session (required for encrypted submission)
310
+ if (sessionId) {
311
+ const existingAuthToken = authToken;
312
+ console.log('[DataUploadService] Getting session key...');
313
+ try {
314
+ authToken = await runWithRetry(() =>
315
+ getSessionKey(this.apiUrl, sessionId)
316
+ );
317
+ console.log('[DataUploadService] ✓ Session key obtained');
318
+ } catch (error) {
319
+ if (existingAuthToken) {
320
+ console.warn(
321
+ '[DataUploadService] Session key refresh failed, using existing token'
322
+ );
323
+ authToken = existingAuthToken;
324
+ } else {
325
+ throw error;
326
+ }
327
+ }
328
+ }
329
+
330
+ if (!authToken) {
331
+ console.log(
332
+ '[DataUploadService] No session key available, skipping upload'
333
+ );
334
+ return false;
335
+ }
336
+
337
+ // Step 1: Submit document data (using same encryption as ResultScreen)
338
+ if (scannedDocument) {
339
+ onProgress?.(0.1);
340
+ await runWithRetry(() =>
341
+ this.submitDocumentData(identificationId, scannedDocument, authToken)
342
+ );
343
+ onProgress?.(0.3);
344
+ }
345
+
346
+ // Step 2: Upload media files (images only, skip video for now - too slow)
347
+ onProgress?.(0.4);
348
+ await runWithRetry(() =>
349
+ this.uploadMedia(
350
+ identificationId,
351
+ scannedDocument,
352
+ livenessDetection,
353
+ (p) => {
354
+ onProgress?.(0.4 + p * 0.5);
355
+ }
356
+ )
357
+ );
358
+ onProgress?.(1.0);
359
+
360
+ console.log('[DataUploadService] ✓ All collected data uploaded');
361
+
362
+ // Mark media as uploaded during video call
363
+ identificationInfo.mediaUploadedDuringVideoCall = true;
364
+
365
+ // Store the auth token back for future use
366
+ identificationInfo.authToken = authToken;
367
+ return true;
368
+ } catch (error) {
369
+ if (error instanceof NotFoundError) {
370
+ console.warn(
371
+ '[DataUploadService] Upload skipped: identification is not active anymore'
372
+ );
373
+ return false;
374
+ }
375
+ console.error(
376
+ '[DataUploadService] Failed to upload collected data:',
377
+ error
378
+ );
379
+ return false;
380
+ }
381
+ }
382
+
383
+ private getGenderEnumType(sex: string | undefined): string {
384
+ switch (sex?.toLowerCase()) {
385
+ case 'male':
386
+ case 'm':
387
+ return 'M';
388
+ case 'female':
389
+ case 'f':
390
+ return 'F';
391
+ default:
392
+ return 'X';
393
+ }
394
+ }
395
+ }
@@ -0,0 +1,190 @@
1
+ export interface VideoSessionInfo {
2
+ sessionId: string;
3
+ identificationId: string;
4
+ callState: 'WAITING' | 'CONNECTING' | 'ACTIVE' | 'COMPLETED' | 'FAILED';
5
+ queuePosition?: number;
6
+ connectedAt?: string;
7
+ }
8
+
9
+ export class VideoSessionService {
10
+ private baseUrl: string;
11
+ private identificationId?: string;
12
+
13
+ constructor(baseUrl: string, identificationId?: string) {
14
+ this.baseUrl = baseUrl;
15
+ this.identificationId = identificationId;
16
+ }
17
+
18
+ /**
19
+ * Get current active video session for customer
20
+ */
21
+ public async getCurrentSession(): Promise<VideoSessionInfo> {
22
+ const params = new URLSearchParams();
23
+ if (this.identificationId) {
24
+ params.append('identificationId', this.identificationId);
25
+ }
26
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/current${params.toString() ? '?' + params.toString() : ''}`;
27
+
28
+ try {
29
+ const response = await fetch(url, {
30
+ method: 'GET',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ });
35
+
36
+ if (!response.ok) {
37
+ throw new Error(
38
+ `Failed to get current session: ${response.statusText}`
39
+ );
40
+ }
41
+
42
+ return await response.json();
43
+ } catch (error) {
44
+ console.error(
45
+ '[VideoSessionService] Error getting current session:',
46
+ error
47
+ );
48
+ throw error;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Join a video session
54
+ */
55
+ public async joinSession(sessionId: string): Promise<VideoSessionInfo> {
56
+ const params = new URLSearchParams();
57
+ if (this.identificationId) {
58
+ params.append('identificationId', this.identificationId);
59
+ }
60
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${sessionId}/join${params.toString() ? '?' + params.toString() : ''}`;
61
+
62
+ try {
63
+ const response = await fetch(url, {
64
+ method: 'POST',
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ },
68
+ });
69
+
70
+ if (!response.ok) {
71
+ throw new Error(`Failed to join session: ${response.statusText}`);
72
+ }
73
+
74
+ return await response.json();
75
+ } catch (error) {
76
+ console.error('[VideoSessionService] Error joining session:', error);
77
+ throw error;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Send a heartbeat to keep the queue-status SSE watchdog alive
83
+ */
84
+ public async sendHeartbeat(sessionId: string): Promise<void> {
85
+ const params = new URLSearchParams();
86
+ if (this.identificationId) {
87
+ params.append('identificationId', this.identificationId);
88
+ }
89
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${sessionId}/heartbeat${params.toString() ? '?' + params.toString() : ''}`;
90
+ try {
91
+ await fetch(url, { method: 'POST' });
92
+ } catch {
93
+ // best-effort, ignore errors
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Leave the session queue (drops customer from WAITING state)
99
+ */
100
+ public async leaveSession(sessionId: string): Promise<void> {
101
+ const params = new URLSearchParams();
102
+ if (this.identificationId) {
103
+ params.append('identificationId', this.identificationId);
104
+ }
105
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${sessionId}/join${params.toString() ? '?' + params.toString() : ''}`;
106
+
107
+ try {
108
+ await fetch(url, { method: 'DELETE' });
109
+ } catch (error) {
110
+ console.error('[VideoSessionService] Error leaving session:', error);
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Subscribe to queue status updates via Server-Sent Events (SSE)
116
+ */
117
+ public subscribeToQueueUpdates(
118
+ sessionId: string,
119
+ onUpdate: (position: number) => void,
120
+ onError: (error: Error) => void
121
+ ): () => void {
122
+ try {
123
+ // Use react-native-sse for real SSE support
124
+ const EventSource = require('react-native-sse').default;
125
+
126
+ const url = `${this.baseUrl}/api/app/mobile/video-sessions/${sessionId}/queue-status?identificationId=${this.identificationId}`;
127
+ console.log('[VideoSessionService] Creating SSE connection to:', url);
128
+
129
+ const es = new EventSource(url);
130
+ let heartbeatInterval: NodeJS.Timeout | null = null;
131
+
132
+ es.addEventListener('open', () => {
133
+ console.log('[VideoSessionService] Queue SSE connected');
134
+ // Send initial heartbeat
135
+ this.sendHeartbeat(sessionId).catch(() => {});
136
+ // Set up periodic heartbeat (every 20s to stay under 30s watchdog)
137
+ heartbeatInterval = setInterval(() => {
138
+ this.sendHeartbeat(sessionId).catch(() => {});
139
+ }, 20000);
140
+ });
141
+
142
+ es.addEventListener('ping', () => {
143
+ // Server sent a ping, respond with heartbeat
144
+ this.sendHeartbeat(sessionId).catch(() => {});
145
+ });
146
+
147
+ es.addEventListener('message', (event: any) => {
148
+ try {
149
+ const data = JSON.parse(event.data);
150
+ console.log('[VideoSessionService] Queue update:', data);
151
+ if (data.queuePosition !== undefined) {
152
+ onUpdate(data.queuePosition);
153
+ }
154
+
155
+ // Close connection if no longer waiting
156
+ if (data.callState !== 'WAITING') {
157
+ if (heartbeatInterval) clearInterval(heartbeatInterval);
158
+ es.close();
159
+ }
160
+ } catch (error) {
161
+ console.error('[SSE] Failed to parse queue update:', error);
162
+ }
163
+ });
164
+
165
+ es.addEventListener('error', (event: any) => {
166
+ console.error('[SSE] Connection error:', event);
167
+ if (heartbeatInterval) clearInterval(heartbeatInterval);
168
+ onError(new Error('SSE connection failed'));
169
+ es.close();
170
+ });
171
+
172
+ // Return cleanup function
173
+ return () => {
174
+ if (heartbeatInterval) clearInterval(heartbeatInterval);
175
+ es.close();
176
+ };
177
+ } catch (error) {
178
+ console.error(
179
+ '[VideoSessionService] Failed to create SSE connection:',
180
+ error
181
+ );
182
+ onError(
183
+ error instanceof Error
184
+ ? error
185
+ : new Error('Failed to create SSE connection')
186
+ );
187
+ return () => {};
188
+ }
189
+ }
190
+ }