@trustchex/react-native-sdk 1.253.0 → 1.266.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 (84) hide show
  1. package/README.md +43 -2
  2. package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
  3. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
  4. package/ios/DeviceBrightnessModule.h +4 -0
  5. package/ios/DeviceBrightnessModule.m +27 -0
  6. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
  7. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
  8. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
  9. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
  10. package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
  11. package/lib/module/Screens/Static/ResultScreen.js +52 -3
  12. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +41 -2
  13. package/lib/module/Shared/Components/EIDScanner.js +63 -3
  14. package/lib/module/Shared/Components/FaceCamera.js +69 -4
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.js +4 -1
  16. package/lib/module/Shared/Components/NavigationManager.js +2 -0
  17. package/lib/module/Shared/Components/QrCodeScannerCamera.js +2 -0
  18. package/lib/module/Shared/Contexts/AppContext.js +3 -1
  19. package/lib/module/Shared/Libs/analytics.utils.js +430 -0
  20. package/lib/module/Shared/Libs/camera.utils.js +58 -2
  21. package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
  22. package/lib/module/Shared/Libs/http-client.js +89 -28
  23. package/lib/module/Shared/Services/AnalyticsService.js +404 -0
  24. package/lib/module/Shared/Types/analytics.types.js +111 -0
  25. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
  26. package/lib/module/Translation/index.js +5 -0
  27. package/lib/module/Trustchex.js +47 -4
  28. package/lib/module/index.js +3 -0
  29. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  30. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  31. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  32. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  33. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  35. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  36. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  37. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
  38. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  39. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  42. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  43. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
  44. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
  45. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
  46. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  48. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
  50. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
  51. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
  52. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
  53. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
  54. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  55. package/lib/typescript/src/Translation/index.d.ts.map +1 -1
  56. package/lib/typescript/src/Trustchex.d.ts +1 -0
  57. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  58. package/lib/typescript/src/index.d.ts +4 -0
  59. package/lib/typescript/src/index.d.ts.map +1 -1
  60. package/package.json +6 -2
  61. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
  62. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
  63. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
  64. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
  65. package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
  66. package/src/Screens/Static/ResultScreen.tsx +79 -4
  67. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +65 -10
  68. package/src/Shared/Components/EIDScanner.tsx +132 -3
  69. package/src/Shared/Components/FaceCamera.tsx +77 -2
  70. package/src/Shared/Components/IdentityDocumentCamera.tsx +4 -4
  71. package/src/Shared/Components/NavigationManager.tsx +2 -0
  72. package/src/Shared/Components/QrCodeScannerCamera.tsx +2 -0
  73. package/src/Shared/Contexts/AppContext.ts +4 -0
  74. package/src/Shared/Libs/analytics.utils.ts +644 -0
  75. package/src/Shared/Libs/camera.utils.ts +74 -2
  76. package/src/Shared/Libs/deeplink.utils.ts +5 -0
  77. package/src/Shared/Libs/http-client.ts +105 -31
  78. package/src/Shared/Services/AnalyticsService.ts +470 -0
  79. package/src/Shared/Types/analytics.types.ts +179 -0
  80. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
  81. package/src/Translation/Resources/tr.ts +2 -1
  82. package/src/Translation/index.ts +9 -0
  83. package/src/Trustchex.tsx +54 -2
  84. package/src/index.tsx +33 -0
@@ -0,0 +1,404 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Analytics Service - GDPR/KVKK/PCI-DSS Compliant
5
+ * Sends events immediately when they occur, sanitizes PII
6
+ */
7
+
8
+ import { AppState } from 'react-native';
9
+ import DeviceInfo from 'react-native-device-info';
10
+ import RNFS from 'react-native-fs';
11
+ import 'react-native-get-random-values';
12
+ import { v4 as uuidv4 } from 'uuid';
13
+ import packageJson from '../../../package.json';
14
+ import { AnalyticsEventName } from "../Types/analytics.types.js";
15
+ class AnalyticsService {
16
+ config = null;
17
+ sessionId = null;
18
+ deviceInfo = null;
19
+ isDemoSession = false;
20
+
21
+ // Batching configuration
22
+ queue = [];
23
+ BATCH_SIZE = 10;
24
+ MAX_QUEUE_SIZE = 100; // Prevent memory issues
25
+ FLUSH_INTERVAL = 5000; // 5 seconds
26
+ QUEUE_FILE_PATH = `${RNFS.CachesDirectoryPath}/trustchex_analytics_queue.json`;
27
+ flushTimer = null;
28
+ isFlushing = false;
29
+ appStateSubscription = null;
30
+
31
+ // Critical events that should flush immediately
32
+ CRITICAL_EVENTS = [AnalyticsEventName.SESSION_END, AnalyticsEventName.VERIFICATION_SUCCESS, AnalyticsEventName.VERIFICATION_FAILED];
33
+
34
+ /**
35
+ * Set demo session flag to disable analytics for demo sessions
36
+ */
37
+ setDemoSession(isDemoSession) {
38
+ this.isDemoSession = isDemoSession;
39
+ }
40
+
41
+ /**
42
+ * Initialize the analytics service
43
+ * @param config Analytics configuration with required verification sessionId
44
+ */
45
+ async initialize(config) {
46
+ // Prevent double initialization
47
+ if (this.config) {
48
+ return;
49
+ }
50
+
51
+ // Validate required verificationSessionId
52
+ if (!config.verificationSessionId) {
53
+ throw new Error('Analytics initialization failed: verificationSessionId is required (must be verification session ID from backend)');
54
+ }
55
+
56
+ // Merge with defaults
57
+ this.config = {
58
+ enabled: config.enabled ?? true,
59
+ baseUrl: config.baseUrl,
60
+ verificationSessionId: config.verificationSessionId
61
+ };
62
+ this.sessionId = config.verificationSessionId;
63
+
64
+ // Load persisted queue
65
+ await this.loadPersistedQueue();
66
+
67
+ // Collect anonymized device information
68
+ await this.collectDeviceInfo();
69
+
70
+ // Start flush timer
71
+ this.startFlushTimer();
72
+
73
+ // Listen for app state changes to flush before backgrounding
74
+ this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange.bind(this));
75
+ }
76
+
77
+ /**
78
+ * Handle app state changes - flush when going to background
79
+ */
80
+ handleAppStateChange(nextAppState) {
81
+ if (nextAppState === 'background' || nextAppState === 'inactive') {
82
+ // Persist queue first to ensure data safety, then flush
83
+ // Note: We don't await here as AppState callbacks are sync,
84
+ // but the operations are queued and will complete
85
+ this.persistQueue().then(() => this.flush());
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Load persisted queue from disk
91
+ */
92
+ async loadPersistedQueue() {
93
+ try {
94
+ if (await RNFS.exists(this.QUEUE_FILE_PATH)) {
95
+ const content = await RNFS.readFile(this.QUEUE_FILE_PATH, 'utf8');
96
+ const persistedQueue = JSON.parse(content);
97
+ if (Array.isArray(persistedQueue) && persistedQueue.length > 0) {
98
+ // Merge with existing queue, prioritizing persisted events
99
+ this.queue = [...persistedQueue, ...this.queue];
100
+ if (__DEV__) console.log(`[Analytics] Loaded ${persistedQueue.length} events from disk`);
101
+ }
102
+ // Clear the file after loading to prevent duplicate loads
103
+ await RNFS.unlink(this.QUEUE_FILE_PATH);
104
+ }
105
+ } catch (error) {
106
+ if (__DEV__) console.warn('[Analytics] Failed to load persisted queue:', error);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Persist queue to disk
112
+ */
113
+ async persistQueue() {
114
+ try {
115
+ if (this.queue.length === 0) {
116
+ if (await RNFS.exists(this.QUEUE_FILE_PATH)) {
117
+ await RNFS.unlink(this.QUEUE_FILE_PATH);
118
+ }
119
+ return;
120
+ }
121
+ await RNFS.writeFile(this.QUEUE_FILE_PATH, JSON.stringify(this.queue), 'utf8');
122
+ } catch (error) {
123
+ if (__DEV__) console.warn('[Analytics] Failed to persist queue:', error);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Track an analytics event - adds to queue and flushes if full
129
+ */
130
+ async trackEvent(eventName, category, metadata) {
131
+ // Skip analytics for demo sessions
132
+ if (this.isDemoSession) {
133
+ return;
134
+ }
135
+
136
+ // Skip if not initialized or disabled
137
+ if (!this.config?.enabled || !this.sessionId) {
138
+ return;
139
+ }
140
+
141
+ // Collect device info on first event if not already done
142
+ if (!this.deviceInfo) {
143
+ await this.collectDeviceInfo();
144
+ }
145
+
146
+ // Sanitize metadata to remove any potential PII
147
+ const sanitizedMetadata = this.sanitizeMetadata({
148
+ ...metadata,
149
+ deviceModel: DeviceInfo.getModel()
150
+ });
151
+ const event = {
152
+ eventId: uuidv4(),
153
+ verificationSessionId: this.sessionId,
154
+ eventName,
155
+ category,
156
+ timestamp: new Date().toISOString(),
157
+ deviceInfo: this.deviceInfo,
158
+ metadata: sanitizedMetadata
159
+ };
160
+
161
+ // Check if this is a critical event that should flush immediately
162
+ const isCriticalEvent = this.CRITICAL_EVENTS.includes(eventName);
163
+
164
+ // Add to queue
165
+ this.queue.push(event);
166
+
167
+ // Drop oldest events if queue exceeds max size (prevent memory issues)
168
+ if (this.queue.length > this.MAX_QUEUE_SIZE) {
169
+ const dropped = this.queue.length - this.MAX_QUEUE_SIZE;
170
+ this.queue = this.queue.slice(dropped);
171
+ if (__DEV__) console.warn(`[Analytics] Queue overflow, dropped ${dropped} oldest events`);
172
+ }
173
+
174
+ // Flush immediately for critical events or if batch size reached
175
+ if (isCriticalEvent || this.queue.length >= this.BATCH_SIZE) {
176
+ await this.flush();
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Start the periodic flush timer
182
+ */
183
+ startFlushTimer() {
184
+ if (this.flushTimer) return;
185
+ this.flushTimer = setInterval(() => {
186
+ this.flush();
187
+ }, this.FLUSH_INTERVAL);
188
+ }
189
+
190
+ /**
191
+ * Stop the flush timer
192
+ */
193
+ stopFlushTimer() {
194
+ if (this.flushTimer) {
195
+ clearInterval(this.flushTimer);
196
+ this.flushTimer = null;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Flush the event queue
202
+ */
203
+ async flush() {
204
+ // Early return checks - must be atomic with setting isFlushing
205
+ if (this.queue.length === 0 || this.isFlushing || !this.config) {
206
+ return;
207
+ }
208
+
209
+ // Set flag immediately to prevent race conditions
210
+ this.isFlushing = true;
211
+
212
+ // Take a batch, but don't remove from queue yet until success
213
+ const batchSize = Math.min(this.queue.length, this.BATCH_SIZE);
214
+ const batch = this.queue.slice(0, batchSize);
215
+
216
+ // Double-check batch has items (defensive programming)
217
+ if (batch.length === 0) {
218
+ this.isFlushing = false;
219
+ return;
220
+ }
221
+ try {
222
+ await this.sendBatchWithRetry(batch);
223
+
224
+ // Remove sent events
225
+ this.queue = this.queue.slice(batchSize);
226
+
227
+ // Update persistence
228
+ await this.persistQueue();
229
+ } catch (error) {
230
+ // On failure, we keep events in the queue
231
+ if (__DEV__) console.warn('[Analytics] Failed to flush batch, keeping events:', error);
232
+
233
+ // Ensure they are persisted
234
+ await this.persistQueue();
235
+ } finally {
236
+ this.isFlushing = false;
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Send batch with retry logic (exponential backoff: 1s, 2s, 4s)
242
+ */
243
+ async sendBatchWithRetry(events, maxRetries = 3, attempt = 1) {
244
+ if (!this.config || events.length === 0) return;
245
+ try {
246
+ const response = await fetch(`${this.config.baseUrl}/api/app/mobile/analytics`, {
247
+ method: 'POST',
248
+ headers: {
249
+ 'Content-Type': 'application/json'
250
+ },
251
+ body: JSON.stringify(events)
252
+ });
253
+ if (!response.ok) {
254
+ if (attempt <= maxRetries && response.status >= 500) {
255
+ // Retry on server errors with exponential backoff
256
+ const delay = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
257
+ await new Promise(resolve => setTimeout(resolve, delay));
258
+ return this.sendBatchWithRetry(events, maxRetries, attempt + 1);
259
+ }
260
+ // For 429 (Too Many Requests), throw to retry
261
+ if (response.status === 429) {
262
+ throw new Error('Rate limit exceeded');
263
+ }
264
+
265
+ // For other 4xx errors (Bad Request, Unauthorized, etc.), do NOT retry.
266
+ // We return successfully so the bad events are removed from the queue.
267
+ if (response.status >= 400 && response.status < 500) {
268
+ if (__DEV__) console.warn(`[Analytics] Dropping batch due to ${response.status} error`);
269
+ return;
270
+ }
271
+ if (__DEV__) console.warn('[Analytics] Failed to send batch:', response.status);
272
+ }
273
+ } catch (error) {
274
+ if (attempt <= maxRetries) {
275
+ // Retry on network errors with exponential backoff
276
+ const delay = Math.pow(2, attempt - 1) * 1000;
277
+ await new Promise(resolve => setTimeout(resolve, delay));
278
+ return this.sendBatchWithRetry(events, maxRetries, attempt + 1);
279
+ }
280
+ throw error;
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Clear analytics session and reset all state
286
+ */
287
+ async clear() {
288
+ // Flush any pending events before clearing
289
+ await this.flush();
290
+ this.stopFlushTimer();
291
+
292
+ // Remove AppState listener
293
+ if (this.appStateSubscription) {
294
+ this.appStateSubscription.remove();
295
+ this.appStateSubscription = null;
296
+ }
297
+ this.queue = [];
298
+ this.sessionId = null;
299
+ this.deviceInfo = null;
300
+ this.config = null;
301
+ }
302
+
303
+ /**
304
+ * Check if analytics is enabled
305
+ */
306
+ isEnabled() {
307
+ return this.config?.enabled === true && this.sessionId !== null;
308
+ }
309
+
310
+ /**
311
+ * Private: Collect anonymized device information
312
+ */
313
+ async collectDeviceInfo() {
314
+ try {
315
+ const platform = DeviceInfo.getSystemName().toLowerCase();
316
+ this.deviceInfo = {
317
+ platform: platform,
318
+ osVersion: DeviceInfo.getSystemVersion(),
319
+ appVersion: DeviceInfo.getVersion(),
320
+ sdkVersion: packageJson.version,
321
+ locale: 'en',
322
+ // Will be set from app context
323
+ timezone: new Date().toTimeString().split(' ')[1] || 'UTC',
324
+ screenResolution: `${DeviceInfo.getDeviceType()}`
325
+ };
326
+ } catch (error) {
327
+ if (__DEV__) console.warn('[Analytics] Error collecting device info:', error);
328
+ // Fallback device info
329
+ this.deviceInfo = {
330
+ platform: 'android',
331
+ osVersion: 'unknown',
332
+ appVersion: 'unknown',
333
+ sdkVersion: packageJson.version,
334
+ locale: 'en',
335
+ timezone: 'UTC',
336
+ screenResolution: 'unknown'
337
+ };
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Update device locale from app context
343
+ */
344
+ setLocale(locale) {
345
+ if (this.deviceInfo) {
346
+ this.deviceInfo.locale = locale;
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Private: Sanitize metadata to remove PII
352
+ */
353
+ sanitizeMetadata(metadata) {
354
+ if (!metadata) return undefined;
355
+ const sanitized = {};
356
+ const forbiddenKeys = ['email', 'emailaddress', 'phone', 'phonenumber', 'firstname', 'lastname', 'fullname', 'address', 'streetaddress', 'ssn', 'socialsecurity', 'passport', 'passportnumber', 'driverlicense', 'creditcard', 'cardnumber', 'cvv', 'password', 'apikey', 'accesstoken'];
357
+ for (const [key, value] of Object.entries(metadata)) {
358
+ const lowerKey = key.toLowerCase();
359
+
360
+ // Skip if key is exactly a forbidden key or contains it as a word boundary
361
+ // But allow compound words like "buttonName", "screenName"
362
+ const isForbidden = forbiddenKeys.some(forbidden => {
363
+ // Check for exact match
364
+ if (lowerKey === forbidden) return true;
365
+
366
+ // Check if forbidden word appears at start followed by non-letter
367
+ if (lowerKey.startsWith(forbidden) && lowerKey.length > forbidden.length) {
368
+ const nextChar = lowerKey[forbidden.length];
369
+ if (!/[a-z]/.test(nextChar)) return true;
370
+ }
371
+
372
+ // Check if forbidden word appears at end preceded by non-letter
373
+ if (lowerKey.endsWith(forbidden) && lowerKey.length > forbidden.length) {
374
+ const prevChar = lowerKey[lowerKey.length - forbidden.length - 1];
375
+ if (!/[a-z]/.test(prevChar)) return true;
376
+ }
377
+ return false;
378
+ });
379
+ if (isForbidden) {
380
+ continue;
381
+ }
382
+
383
+ // Skip if value looks like email
384
+ if (typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
385
+ continue;
386
+ }
387
+
388
+ // Skip if value looks like phone number
389
+ if (typeof value === 'string' && /^\+?[\d\s\-()]{10,}$/.test(value)) {
390
+ continue;
391
+ }
392
+
393
+ // Skip if value looks like credit card
394
+ if (typeof value === 'string' && /^\d{13,19}$/.test(value)) {
395
+ continue;
396
+ }
397
+ sanitized[key] = value;
398
+ }
399
+ return sanitized;
400
+ }
401
+ }
402
+
403
+ // Singleton instance
404
+ export const analyticsService = new AnalyticsService();
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Analytics Types for Trustchex React Native SDK
5
+ * GDPR, KVKK, and PCI-DSS Compliant Analytics System
6
+ */
7
+
8
+ /**
9
+ * Categories of analytics events
10
+ */
11
+ export let AnalyticsEventCategory = /*#__PURE__*/function (AnalyticsEventCategory) {
12
+ AnalyticsEventCategory["SESSION"] = "session";
13
+ AnalyticsEventCategory["NAVIGATION"] = "navigation";
14
+ AnalyticsEventCategory["USER_ACTION"] = "user_action";
15
+ AnalyticsEventCategory["ERROR"] = "error";
16
+ AnalyticsEventCategory["PERFORMANCE"] = "performance";
17
+ AnalyticsEventCategory["VERIFICATION"] = "verification";
18
+ return AnalyticsEventCategory;
19
+ }({});
20
+
21
+ /**
22
+ * Error severity levels for analytics tracking
23
+ */
24
+
25
+ /**
26
+ * Error categories for grouping similar errors
27
+ */
28
+ export let ErrorCategory = /*#__PURE__*/function (ErrorCategory) {
29
+ ErrorCategory["NETWORK"] = "network";
30
+ ErrorCategory["PERMISSION"] = "permission";
31
+ ErrorCategory["CAMERA"] = "camera";
32
+ ErrorCategory["NFC"] = "nfc";
33
+ ErrorCategory["VALIDATION"] = "validation";
34
+ ErrorCategory["API"] = "api";
35
+ ErrorCategory["STORAGE"] = "storage";
36
+ ErrorCategory["DEVICE"] = "device";
37
+ ErrorCategory["USER_INPUT"] = "user_input";
38
+ ErrorCategory["SYSTEM"] = "system";
39
+ ErrorCategory["UNKNOWN"] = "unknown";
40
+ return ErrorCategory;
41
+ }({});
42
+
43
+ /**
44
+ * Context information for error tracking
45
+ */
46
+
47
+ /**
48
+ * Event names for different analytics events
49
+ */
50
+ export let AnalyticsEventName = /*#__PURE__*/function (AnalyticsEventName) {
51
+ // Session Events
52
+ AnalyticsEventName["SESSION_START"] = "session_start";
53
+ AnalyticsEventName["SESSION_END"] = "session_end";
54
+ // Navigation Events
55
+ AnalyticsEventName["SCREEN_VIEW"] = "screen_view";
56
+ AnalyticsEventName["SCREEN_EXIT"] = "screen_exit";
57
+ // User Action Events
58
+ AnalyticsEventName["BUTTON_CLICK"] = "button_click";
59
+ AnalyticsEventName["CONSENT_GIVEN"] = "consent_given";
60
+ AnalyticsEventName["STEP_SKIPPED"] = "step_skipped";
61
+ AnalyticsEventName["STEP_ABANDONED"] = "step_abandoned";
62
+ // Workflow Step Events (started/completed)
63
+ AnalyticsEventName["CONTRACT_ACCEPTANCE_STARTED"] = "contract_acceptance_started";
64
+ AnalyticsEventName["CONTRACT_ACCEPTANCE_COMPLETED"] = "contract_acceptance_completed";
65
+ AnalyticsEventName["DOCUMENT_SCAN_STARTED"] = "identity_document_scan_started";
66
+ AnalyticsEventName["DOCUMENT_SCAN_COMPLETED"] = "identity_document_scan_completed";
67
+ AnalyticsEventName["IDENTITY_DOCUMENT_EID_SCAN_STARTED"] = "identity_document_eid_scan_started";
68
+ AnalyticsEventName["IDENTITY_DOCUMENT_EID_SCAN_COMPLETED"] = "identity_document_eid_scan_completed";
69
+ AnalyticsEventName["LIVENESS_CHECK_STARTED"] = "liveness_check_started";
70
+ AnalyticsEventName["LIVENESS_CHECK_COMPLETED"] = "liveness_check_completed";
71
+ // NFC Scan Events (used by trackNFCScan* helpers)
72
+ AnalyticsEventName["NFC_SCAN_STARTED"] = "nfc_scan_started";
73
+ AnalyticsEventName["NFC_SCAN_COMPLETED"] = "nfc_scan_completed";
74
+ // NFC Error Events (specific error types for NFC failures)
75
+ AnalyticsEventName["NFC_SCAN_FAILED"] = "nfc_scan_failed";
76
+ AnalyticsEventName["NFC_DEVICE_UNSUPPORTED"] = "nfc_device_unsupported";
77
+ AnalyticsEventName["NFC_READING_ERROR"] = "nfc_reading_error";
78
+ AnalyticsEventName["NFC_USER_CANCELLED"] = "nfc_user_cancelled";
79
+ // EID Error Events
80
+ AnalyticsEventName["IDENTITY_DOCUMENT_EID_SCAN_FAILED"] = "identity_document_eid_scan_failed";
81
+ // Performance Events
82
+ AnalyticsEventName["API_CALL_DURATION"] = "api_call_duration";
83
+ // Verification Outcome Events
84
+ AnalyticsEventName["VERIFICATION_SUCCESS"] = "verification_success";
85
+ AnalyticsEventName["VERIFICATION_FAILED"] = "verification_failed";
86
+ return AnalyticsEventName;
87
+ }({});
88
+
89
+ /**
90
+ * Device information (anonymized and privacy-compliant)
91
+ */
92
+
93
+ /**
94
+ * Analytics event metadata
95
+ */
96
+
97
+ /**
98
+ * Base analytics event structure
99
+ */
100
+
101
+ /**
102
+ * Analytics configuration
103
+ */
104
+
105
+ /**
106
+ * Analytics service interface
107
+ */
108
+
109
+ /**
110
+ * Enhanced error event metadata
111
+ */
@@ -15,6 +15,7 @@ export const useCameraPermissions = () => {
15
15
  const requestPermission = async () => {
16
16
  const permission = await requestCameraPermission();
17
17
  if (!permission) {
18
+ // Camera permission denied by user - their choice, not actionable
18
19
  Alert.alert('Camera Permission Required', 'This app needs camera access to scan barcodes. Please enable camera permissions in your device settings.', [{
19
20
  text: 'Cancel',
20
21
  style: 'cancel'
@@ -3,6 +3,7 @@
3
3
  import i18n from 'i18next';
4
4
  import { initReactI18next } from 'react-i18next';
5
5
  import * as resources from "./Resources/index.js";
6
+ import { trackError } from "../Shared/Libs/analytics.utils.js";
6
7
  const getCurrentLanguage = () => {
7
8
  try {
8
9
  if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
@@ -15,6 +16,10 @@ const getCurrentLanguage = () => {
15
16
  return 'en';
16
17
  } catch (error) {
17
18
  console.error('Error detecting language:', error);
19
+ trackError('LANGUAGE_DETECTION_ERROR', error instanceof Error ? error.message : 'Unknown error', 'translation_init', 'low', {
20
+ recoverable: true,
21
+ userAction: 'detect_language'
22
+ }).catch(() => {});
18
23
  return 'en';
19
24
  }
20
25
  };
@@ -16,6 +16,8 @@ import QrCodeScanningScreen from "./Screens/Static/QrCodeScanningScreen.js";
16
16
  import AppContext from "./Shared/Contexts/AppContext.js";
17
17
  import i18n from "./Translation/index.js";
18
18
  import { initializeTTS } from "./Shared/Libs/tts.utils.js";
19
+ import { analyticsService } from "./Shared/Services/AnalyticsService.js";
20
+ import { AnalyticsEventCategory, AnalyticsEventName } from "./Shared/Types/analytics.types.js";
19
21
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
22
  const Stack = createNativeStackNavigator();
21
23
  const DEFAULT_BRANDING = {
@@ -30,23 +32,32 @@ const Trustchex = ({
30
32
  branding: propBranding,
31
33
  locale: propLocale,
32
34
  onCompleted,
33
- onError
35
+ onError,
36
+ enableAnalytics = true
34
37
  }) => {
38
+ useEffect(() => {
39
+ console.log('[Trustchex] Props updated:', {
40
+ baseUrl: propBaseUrl,
41
+ sessionId: propSessionId,
42
+ enableAnalytics
43
+ });
44
+ }, [propBaseUrl, propSessionId, enableAnalytics]);
35
45
  const [baseUrl, setBaseUrl] = useState(null);
36
46
  const [sessionId, setSessionId] = useState(null);
37
47
  const [locale, setLocale] = useState(propLocale || i18n.language);
38
48
  const [isInitialized, setIsInitialized] = useState(false);
49
+ const [analyticsInitialized, setAnalyticsInitialized] = useState(false);
39
50
  const branding = useMemo(() => ({
40
51
  ...DEFAULT_BRANDING,
41
52
  ...propBranding
42
53
  }), [propBranding]);
43
54
  const contextValue = useMemo(() => ({
44
55
  isDemoSession: false,
45
- baseUrl,
56
+ baseUrl: baseUrl || '',
46
57
  locale,
47
58
  branding,
48
59
  identificationInfo: {
49
- sessionId,
60
+ sessionId: sessionId || '',
50
61
  identificationId: '',
51
62
  consent: {
52
63
  contractIds: [],
@@ -55,8 +66,39 @@ const Trustchex = ({
55
66
  locale: propLocale || i18n.language
56
67
  },
57
68
  onCompleted,
58
- onError
69
+ onError,
70
+ setSessionId,
71
+ setBaseUrl
59
72
  }), [baseUrl, locale, branding, sessionId, propLocale, onCompleted, onError]);
73
+
74
+ // Initialize analytics IMMEDIATELY when SDK mounts or session ID changes
75
+ useEffect(() => {
76
+ console.log('[Trustchex] Analytics check:', {
77
+ enableAnalytics,
78
+ hasBaseUrl: !!baseUrl,
79
+ hasSessionId: !!sessionId,
80
+ analyticsInitialized,
81
+ sessionId: sessionId?.substring(0, 8) + '...'
82
+ });
83
+ if (enableAnalytics && baseUrl && sessionId && !analyticsInitialized) {
84
+ console.log('[Trustchex] Initializing analytics...');
85
+ // Initialize analytics synchronously with the verification sessionId
86
+ analyticsService.initialize({
87
+ enabled: true,
88
+ baseUrl: baseUrl,
89
+ verificationSessionId: sessionId // Pass the verification session ID from backend
90
+ }).then(() => {
91
+ console.log('[Trustchex] Analytics initialized successfully');
92
+ setAnalyticsInitialized(true);
93
+ // Track session start as the very first event
94
+ analyticsService.trackEvent(AnalyticsEventName.SESSION_START, AnalyticsEventCategory.SESSION, {
95
+ source: 'sdk_init'
96
+ });
97
+ }).catch(error => {
98
+ console.warn('[Trustchex] Failed to initialize analytics:', error);
99
+ });
100
+ }
101
+ }, [enableAnalytics, baseUrl, sessionId, analyticsInitialized]);
60
102
  useEffect(() => {
61
103
  initializeTTS();
62
104
  }, []);
@@ -76,6 +118,7 @@ const Trustchex = ({
76
118
  if (propLocale) {
77
119
  setLocale(propLocale);
78
120
  i18n.changeLanguage(propLocale);
121
+ analyticsService.setLocale(propLocale);
79
122
  }
80
123
  }, [propLocale]);
81
124
  if (!baseUrl || !isInitialized) {
@@ -2,4 +2,7 @@
2
2
 
3
3
  import Trustchex from "./Trustchex.js";
4
4
  export { handleDeepLink } from "./Shared/Libs/deeplink.utils.js";
5
+ export { analyticsService } from "./Shared/Services/AnalyticsService.js";
6
+ export { trackScreenView, trackScreenExit, trackButtonClick, trackError, trackErrorWithDetails, trackApiCall, trackVerificationStart, trackVerificationComplete, trackConsentGiven, trackSessionStart, trackSessionEnd, trackNFCScanStart, trackNFCScanComplete, trackNFCScanFailed, trackFunnelStep, trackStepAbandoned, trackStepSkipped, useScreenTracking } from "./Shared/Libs/analytics.utils.js";
7
+ export { AnalyticsEventCategory, AnalyticsEventName, ErrorCategory } from "./Shared/Types/analytics.types.js";
5
8
  export default Trustchex;
@@ -1 +1 @@
1
- {"version":3,"file":"ContractAcceptanceScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/ContractAcceptanceScreen.tsx"],"names":[],"mappings":"AAkBA,QAAA,MAAM,wBAAwB,+CAyH7B,CAAC;AA4BF,eAAe,wBAAwB,CAAC"}
1
+ {"version":3,"file":"ContractAcceptanceScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/ContractAcceptanceScreen.tsx"],"names":[],"mappings":"AA0BA,QAAA,MAAM,wBAAwB,+CAmJ7B,CAAC;AA4BF,eAAe,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentEIDScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx"],"names":[],"mappings":"AAaA,QAAA,MAAM,iCAAiC,+CA2KtC,CAAC;AAoBF,eAAe,iCAAiC,CAAC"}
1
+ {"version":3,"file":"IdentityDocumentEIDScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx"],"names":[],"mappings":"AAmBA,QAAA,MAAM,iCAAiC,+CAmMtC,CAAC;AAoBF,eAAe,iCAAiC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"IdentityDocumentScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx"],"names":[],"mappings":"AAaA,QAAA,MAAM,8BAA8B,+CA0HnC,CAAC;AAgBF,eAAe,8BAA8B,CAAC"}
1
+ {"version":3,"file":"IdentityDocumentScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx"],"names":[],"mappings":"AAmBA,QAAA,MAAM,8BAA8B,+CAkJnC,CAAC;AAgBF,eAAe,8BAA8B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"LivenessDetectionScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/LivenessDetectionScreen.tsx"],"names":[],"mappings":"AA2EA,QAAA,MAAM,uBAAuB,+CAugB5B,CAAC;AAwGF,eAAe,uBAAuB,CAAC"}
1
+ {"version":3,"file":"LivenessDetectionScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Dynamic/LivenessDetectionScreen.tsx"],"names":[],"mappings":"AAiFA,QAAA,MAAM,uBAAuB,+CA2hB5B,CAAC;AAwGF,eAAe,uBAAuB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"QrCodeScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/QrCodeScanningScreen.tsx"],"names":[],"mappings":"AAQA,QAAA,MAAM,oBAAoB,+CAmBzB,CAAC;AASF,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"QrCodeScanningScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/QrCodeScanningScreen.tsx"],"names":[],"mappings":"AAQA,QAAA,MAAM,oBAAoB,+CA6BzB,CAAC;AASF,eAAe,oBAAoB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ResultScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/ResultScreen.tsx"],"names":[],"mappings":"AAuCA,QAAA,MAAM,YAAY,+CAwuBjB,CAAC;AAsGF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"ResultScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/ResultScreen.tsx"],"names":[],"mappings":"AAiDA,QAAA,MAAM,YAAY,+CAyyBjB,CAAC;AAsGF,eAAe,YAAY,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"VerificationSessionCheckScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/VerificationSessionCheckScreen.tsx"],"names":[],"mappings":"AA2CA,QAAA,MAAM,8BAA8B,+CA8XnC,CAAC;AAuHF,eAAe,8BAA8B,CAAC"}
1
+ {"version":3,"file":"VerificationSessionCheckScreen.d.ts","sourceRoot":"","sources":["../../../../../src/Screens/Static/VerificationSessionCheckScreen.tsx"],"names":[],"mappings":"AAiDA,QAAA,MAAM,8BAA8B,+CA+anC,CAAC;AAuHF,eAAe,8BAA8B,CAAC"}