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