@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,470 @@
1
+ /**
2
+ * Analytics Service - GDPR/KVKK/PCI-DSS Compliant
3
+ * Sends events immediately when they occur, sanitizes PII
4
+ */
5
+
6
+ import { AppState, type AppStateStatus } from 'react-native';
7
+ import DeviceInfo from 'react-native-device-info';
8
+ import RNFS from 'react-native-fs';
9
+ import 'react-native-get-random-values';
10
+ import { v4 as uuidv4 } from 'uuid';
11
+ import packageJson from '../../../package.json';
12
+ import {
13
+ AnalyticsEventCategory,
14
+ AnalyticsEventName,
15
+ } from '../Types/analytics.types';
16
+ import type {
17
+ AnalyticsConfig,
18
+ AnalyticsEvent,
19
+ AnalyticsEventMetadata,
20
+ AnonymizedDeviceInfo,
21
+ IAnalyticsService,
22
+ } from '../Types/analytics.types';
23
+
24
+ class AnalyticsService implements IAnalyticsService {
25
+ private config: AnalyticsConfig | null = null;
26
+ private sessionId: string | null = null;
27
+ private deviceInfo: AnonymizedDeviceInfo | null = null;
28
+ private isDemoSession: boolean = false;
29
+
30
+ // Batching configuration
31
+ private queue: AnalyticsEvent[] = [];
32
+ private readonly BATCH_SIZE = 10;
33
+ private readonly MAX_QUEUE_SIZE = 100; // Prevent memory issues
34
+ private readonly FLUSH_INTERVAL = 5000; // 5 seconds
35
+ private readonly QUEUE_FILE_PATH = `${RNFS.CachesDirectoryPath}/trustchex_analytics_queue.json`;
36
+ private flushTimer: ReturnType<typeof setInterval> | null = null;
37
+ private isFlushing = false;
38
+ private appStateSubscription: { remove: () => void } | null = null;
39
+
40
+ // Critical events that should flush immediately
41
+ private readonly CRITICAL_EVENTS = [
42
+ AnalyticsEventName.SESSION_END,
43
+ AnalyticsEventName.VERIFICATION_SUCCESS,
44
+ AnalyticsEventName.VERIFICATION_FAILED,
45
+ ];
46
+
47
+ /**
48
+ * Set demo session flag to disable analytics for demo sessions
49
+ */
50
+ setDemoSession(isDemoSession: boolean): void {
51
+ this.isDemoSession = isDemoSession;
52
+ }
53
+
54
+ /**
55
+ * Initialize the analytics service
56
+ * @param config Analytics configuration with required verification sessionId
57
+ */
58
+ async initialize(config: AnalyticsConfig): Promise<void> {
59
+ // Prevent double initialization
60
+ if (this.config) {
61
+ return;
62
+ }
63
+
64
+ // Validate required verificationSessionId
65
+ if (!config.verificationSessionId) {
66
+ throw new Error('Analytics initialization failed: verificationSessionId is required (must be verification session ID from backend)');
67
+ }
68
+
69
+ // Merge with defaults
70
+ this.config = {
71
+ enabled: config.enabled ?? true,
72
+ baseUrl: config.baseUrl,
73
+ verificationSessionId: config.verificationSessionId,
74
+ };
75
+
76
+ this.sessionId = config.verificationSessionId;
77
+
78
+ // Load persisted queue
79
+ await this.loadPersistedQueue();
80
+
81
+ // Collect anonymized device information
82
+ await this.collectDeviceInfo();
83
+
84
+ // Start flush timer
85
+ this.startFlushTimer();
86
+
87
+ // Listen for app state changes to flush before backgrounding
88
+ this.appStateSubscription = AppState.addEventListener(
89
+ 'change',
90
+ this.handleAppStateChange.bind(this)
91
+ );
92
+ }
93
+
94
+ /**
95
+ * Handle app state changes - flush when going to background
96
+ */
97
+ private handleAppStateChange(nextAppState: AppStateStatus): void {
98
+ if (nextAppState === 'background' || nextAppState === 'inactive') {
99
+ // Persist queue first to ensure data safety, then flush
100
+ // Note: We don't await here as AppState callbacks are sync,
101
+ // but the operations are queued and will complete
102
+ this.persistQueue().then(() => this.flush());
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Load persisted queue from disk
108
+ */
109
+ private async loadPersistedQueue(): Promise<void> {
110
+ try {
111
+ if (await RNFS.exists(this.QUEUE_FILE_PATH)) {
112
+ const content = await RNFS.readFile(this.QUEUE_FILE_PATH, 'utf8');
113
+ const persistedQueue = JSON.parse(content);
114
+ if (Array.isArray(persistedQueue) && persistedQueue.length > 0) {
115
+ // Merge with existing queue, prioritizing persisted events
116
+ this.queue = [...persistedQueue, ...this.queue];
117
+ if (__DEV__) console.log(`[Analytics] Loaded ${persistedQueue.length} events from disk`);
118
+ }
119
+ // Clear the file after loading to prevent duplicate loads
120
+ await RNFS.unlink(this.QUEUE_FILE_PATH);
121
+ }
122
+ } catch (error) {
123
+ if (__DEV__) console.warn('[Analytics] Failed to load persisted queue:', error);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Persist queue to disk
129
+ */
130
+ private async persistQueue(): Promise<void> {
131
+ try {
132
+ if (this.queue.length === 0) {
133
+ if (await RNFS.exists(this.QUEUE_FILE_PATH)) {
134
+ await RNFS.unlink(this.QUEUE_FILE_PATH);
135
+ }
136
+ return;
137
+ }
138
+ await RNFS.writeFile(this.QUEUE_FILE_PATH, JSON.stringify(this.queue), 'utf8');
139
+ } catch (error) {
140
+ if (__DEV__) console.warn('[Analytics] Failed to persist queue:', error);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Track an analytics event - adds to queue and flushes if full
146
+ */
147
+ async trackEvent(
148
+ eventName: string,
149
+ category: AnalyticsEventCategory,
150
+ metadata?: AnalyticsEventMetadata
151
+ ): Promise<void> {
152
+ // Skip analytics for demo sessions
153
+ if (this.isDemoSession) {
154
+ return;
155
+ }
156
+
157
+ // Skip if not initialized or disabled
158
+ if (!this.config?.enabled || !this.sessionId) {
159
+ return;
160
+ }
161
+
162
+ // Collect device info on first event if not already done
163
+ if (!this.deviceInfo) {
164
+ await this.collectDeviceInfo();
165
+ }
166
+
167
+ // Sanitize metadata to remove any potential PII
168
+ const sanitizedMetadata = this.sanitizeMetadata({
169
+ ...metadata,
170
+ deviceModel: DeviceInfo.getModel(),
171
+ });
172
+
173
+ const event: AnalyticsEvent = {
174
+ eventId: uuidv4(),
175
+ verificationSessionId: this.sessionId,
176
+ eventName,
177
+ category,
178
+ timestamp: new Date().toISOString(),
179
+ deviceInfo: this.deviceInfo!,
180
+ metadata: sanitizedMetadata,
181
+ };
182
+
183
+ // Check if this is a critical event that should flush immediately
184
+ const isCriticalEvent = this.CRITICAL_EVENTS.includes(
185
+ eventName as AnalyticsEventName
186
+ );
187
+
188
+ // Add to queue
189
+ this.queue.push(event);
190
+
191
+ // Drop oldest events if queue exceeds max size (prevent memory issues)
192
+ if (this.queue.length > this.MAX_QUEUE_SIZE) {
193
+ const dropped = this.queue.length - this.MAX_QUEUE_SIZE;
194
+ this.queue = this.queue.slice(dropped);
195
+ if (__DEV__) console.warn(`[Analytics] Queue overflow, dropped ${dropped} oldest events`);
196
+ }
197
+
198
+ // Flush immediately for critical events or if batch size reached
199
+ if (isCriticalEvent || this.queue.length >= this.BATCH_SIZE) {
200
+ await this.flush();
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Start the periodic flush timer
206
+ */
207
+ private startFlushTimer(): void {
208
+ if (this.flushTimer) return;
209
+
210
+ this.flushTimer = setInterval(() => {
211
+ this.flush();
212
+ }, this.FLUSH_INTERVAL);
213
+ }
214
+
215
+ /**
216
+ * Stop the flush timer
217
+ */
218
+ private stopFlushTimer(): void {
219
+ if (this.flushTimer) {
220
+ clearInterval(this.flushTimer);
221
+ this.flushTimer = null;
222
+ }
223
+ }
224
+
225
+ /**
226
+ * Flush the event queue
227
+ */
228
+ async flush(): Promise<void> {
229
+ // Early return checks - must be atomic with setting isFlushing
230
+ if (this.queue.length === 0 || this.isFlushing || !this.config) {
231
+ return;
232
+ }
233
+
234
+ // Set flag immediately to prevent race conditions
235
+ this.isFlushing = true;
236
+
237
+ // Take a batch, but don't remove from queue yet until success
238
+ const batchSize = Math.min(this.queue.length, this.BATCH_SIZE);
239
+ const batch = this.queue.slice(0, batchSize);
240
+
241
+ // Double-check batch has items (defensive programming)
242
+ if (batch.length === 0) {
243
+ this.isFlushing = false;
244
+ return;
245
+ }
246
+
247
+ try {
248
+ await this.sendBatchWithRetry(batch);
249
+
250
+ // Remove sent events
251
+ this.queue = this.queue.slice(batchSize);
252
+
253
+ // Update persistence
254
+ await this.persistQueue();
255
+ } catch (error) {
256
+ // On failure, we keep events in the queue
257
+ if (__DEV__) console.warn('[Analytics] Failed to flush batch, keeping events:', error);
258
+
259
+ // Ensure they are persisted
260
+ await this.persistQueue();
261
+ } finally {
262
+ this.isFlushing = false;
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Send batch with retry logic (exponential backoff: 1s, 2s, 4s)
268
+ */
269
+ private async sendBatchWithRetry(events: AnalyticsEvent[], maxRetries = 3, attempt = 1): Promise<void> {
270
+ if (!this.config || events.length === 0) return;
271
+
272
+ try {
273
+ const response = await fetch(
274
+ `${this.config.baseUrl}/api/app/mobile/analytics`,
275
+ {
276
+ method: 'POST',
277
+ headers: {
278
+ 'Content-Type': 'application/json',
279
+ },
280
+ body: JSON.stringify(events),
281
+ }
282
+ );
283
+
284
+ if (!response.ok) {
285
+ if (attempt <= maxRetries && response.status >= 500) {
286
+ // Retry on server errors with exponential backoff
287
+ const delay = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
288
+ await new Promise(resolve => setTimeout(resolve, delay));
289
+ return this.sendBatchWithRetry(events, maxRetries, attempt + 1);
290
+ }
291
+ // For 429 (Too Many Requests), throw to retry
292
+ if (response.status === 429) {
293
+ throw new Error('Rate limit exceeded');
294
+ }
295
+
296
+ // For other 4xx errors (Bad Request, Unauthorized, etc.), do NOT retry.
297
+ // We return successfully so the bad events are removed from the queue.
298
+ if (response.status >= 400 && response.status < 500) {
299
+ if (__DEV__) console.warn(`[Analytics] Dropping batch due to ${response.status} error`);
300
+ return;
301
+ }
302
+
303
+ if (__DEV__) console.warn('[Analytics] Failed to send batch:', response.status);
304
+ }
305
+ } catch (error) {
306
+ if (attempt <= maxRetries) {
307
+ // Retry on network errors with exponential backoff
308
+ const delay = Math.pow(2, attempt - 1) * 1000;
309
+ await new Promise(resolve => setTimeout(resolve, delay));
310
+ return this.sendBatchWithRetry(events, maxRetries, attempt + 1);
311
+ }
312
+ throw error;
313
+ }
314
+ }
315
+
316
+ /**
317
+ * Clear analytics session and reset all state
318
+ */
319
+ async clear(): Promise<void> {
320
+ // Flush any pending events before clearing
321
+ await this.flush();
322
+
323
+ this.stopFlushTimer();
324
+
325
+ // Remove AppState listener
326
+ if (this.appStateSubscription) {
327
+ this.appStateSubscription.remove();
328
+ this.appStateSubscription = null;
329
+ }
330
+
331
+ this.queue = [];
332
+ this.sessionId = null;
333
+ this.deviceInfo = null;
334
+ this.config = null;
335
+ }
336
+
337
+ /**
338
+ * Check if analytics is enabled
339
+ */
340
+ isEnabled(): boolean {
341
+ return this.config?.enabled === true && this.sessionId !== null;
342
+ }
343
+
344
+ /**
345
+ * Private: Collect anonymized device information
346
+ */
347
+ private async collectDeviceInfo(): Promise<void> {
348
+ try {
349
+ const platform = DeviceInfo.getSystemName().toLowerCase();
350
+
351
+ this.deviceInfo = {
352
+ platform: platform as 'ios' | 'android',
353
+ osVersion: DeviceInfo.getSystemVersion(),
354
+ appVersion: DeviceInfo.getVersion(),
355
+ sdkVersion: packageJson.version,
356
+ locale: 'en', // Will be set from app context
357
+ timezone: new Date().toTimeString().split(' ')[1] || 'UTC',
358
+ screenResolution: `${DeviceInfo.getDeviceType()}`,
359
+ };
360
+ } catch (error) {
361
+ if (__DEV__) console.warn('[Analytics] Error collecting device info:', error);
362
+ // Fallback device info
363
+ this.deviceInfo = {
364
+ platform: 'android',
365
+ osVersion: 'unknown',
366
+ appVersion: 'unknown',
367
+ sdkVersion: packageJson.version,
368
+ locale: 'en',
369
+ timezone: 'UTC',
370
+ screenResolution: 'unknown',
371
+ };
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Update device locale from app context
377
+ */
378
+ public setLocale(locale: string): void {
379
+ if (this.deviceInfo) {
380
+ this.deviceInfo.locale = locale;
381
+ }
382
+ }
383
+
384
+ /**
385
+ * Private: Sanitize metadata to remove PII
386
+ */
387
+ private sanitizeMetadata(
388
+ metadata?: AnalyticsEventMetadata
389
+ ): AnalyticsEventMetadata | undefined {
390
+ if (!metadata) return undefined;
391
+
392
+ const sanitized: AnalyticsEventMetadata = {};
393
+ const forbiddenKeys = [
394
+ 'email',
395
+ 'emailaddress',
396
+ 'phone',
397
+ 'phonenumber',
398
+ 'firstname',
399
+ 'lastname',
400
+ 'fullname',
401
+ 'address',
402
+ 'streetaddress',
403
+ 'ssn',
404
+ 'socialsecurity',
405
+ 'passport',
406
+ 'passportnumber',
407
+ 'driverlicense',
408
+ 'creditcard',
409
+ 'cardnumber',
410
+ 'cvv',
411
+ 'password',
412
+ 'apikey',
413
+ 'accesstoken',
414
+ ];
415
+
416
+ for (const [key, value] of Object.entries(metadata)) {
417
+ const lowerKey = key.toLowerCase();
418
+
419
+ // Skip if key is exactly a forbidden key or contains it as a word boundary
420
+ // But allow compound words like "buttonName", "screenName"
421
+ const isForbidden = forbiddenKeys.some((forbidden) => {
422
+ // Check for exact match
423
+ if (lowerKey === forbidden) return true;
424
+
425
+ // Check if forbidden word appears at start followed by non-letter
426
+ if (lowerKey.startsWith(forbidden) && lowerKey.length > forbidden.length) {
427
+ const nextChar = lowerKey[forbidden.length];
428
+ if (!/[a-z]/.test(nextChar)) return true;
429
+ }
430
+
431
+ // Check if forbidden word appears at end preceded by non-letter
432
+ if (lowerKey.endsWith(forbidden) && lowerKey.length > forbidden.length) {
433
+ const prevChar = lowerKey[lowerKey.length - forbidden.length - 1];
434
+ if (!/[a-z]/.test(prevChar)) return true;
435
+ }
436
+
437
+ return false;
438
+ });
439
+
440
+ if (isForbidden) {
441
+ continue;
442
+ }
443
+
444
+ // Skip if value looks like email
445
+ if (
446
+ typeof value === 'string' &&
447
+ /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
448
+ ) {
449
+ continue;
450
+ }
451
+
452
+ // Skip if value looks like phone number
453
+ if (typeof value === 'string' && /^\+?[\d\s\-()]{10,}$/.test(value)) {
454
+ continue;
455
+ }
456
+
457
+ // Skip if value looks like credit card
458
+ if (typeof value === 'string' && /^\d{13,19}$/.test(value)) {
459
+ continue;
460
+ }
461
+
462
+ sanitized[key] = value;
463
+ }
464
+
465
+ return sanitized;
466
+ }
467
+ }
468
+
469
+ // Singleton instance
470
+ export const analyticsService = new AnalyticsService();
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Analytics Types for Trustchex React Native SDK
3
+ * GDPR, KVKK, and PCI-DSS Compliant Analytics System
4
+ */
5
+
6
+ /**
7
+ * Categories of analytics events
8
+ */
9
+ export enum AnalyticsEventCategory {
10
+ SESSION = 'session',
11
+ NAVIGATION = 'navigation',
12
+ USER_ACTION = 'user_action',
13
+ ERROR = 'error',
14
+ PERFORMANCE = 'performance',
15
+ VERIFICATION = 'verification',
16
+ }
17
+
18
+ /**
19
+ * Error severity levels for analytics tracking
20
+ */
21
+ export type ErrorSeverity = 'low' | 'medium' | 'high' | 'critical';
22
+
23
+ /**
24
+ * Error categories for grouping similar errors
25
+ */
26
+ export enum ErrorCategory {
27
+ NETWORK = 'network',
28
+ PERMISSION = 'permission',
29
+ CAMERA = 'camera',
30
+ NFC = 'nfc',
31
+ VALIDATION = 'validation',
32
+ API = 'api',
33
+ STORAGE = 'storage',
34
+ DEVICE = 'device',
35
+ USER_INPUT = 'user_input',
36
+ SYSTEM = 'system',
37
+ UNKNOWN = 'unknown',
38
+ }
39
+
40
+ /**
41
+ * Context information for error tracking
42
+ */
43
+ export interface ErrorContext {
44
+ /** The component or module where the error occurred */
45
+ component?: string;
46
+ /** User action that triggered the error */
47
+ userAction?: string;
48
+ /** Whether the error is recoverable */
49
+ recoverable?: boolean;
50
+ /** Number of retry attempts made */
51
+ retryCount?: number;
52
+ /** Sanitized stack trace (no PII) */
53
+ stackTrace?: string;
54
+ /** Additional context data */
55
+ additionalData?: Record<string, string | number | boolean | null>;
56
+ }
57
+
58
+ /**
59
+ * Event names for different analytics events
60
+ */
61
+ export enum AnalyticsEventName {
62
+ // Session Events
63
+ SESSION_START = 'session_start',
64
+ SESSION_END = 'session_end',
65
+
66
+ // Navigation Events
67
+ SCREEN_VIEW = 'screen_view',
68
+ SCREEN_EXIT = 'screen_exit',
69
+
70
+ // User Action Events
71
+ BUTTON_CLICK = 'button_click',
72
+ CONSENT_GIVEN = 'consent_given',
73
+ STEP_SKIPPED = 'step_skipped',
74
+ STEP_ABANDONED = 'step_abandoned',
75
+
76
+ // Workflow Step Events (started/completed)
77
+ CONTRACT_ACCEPTANCE_STARTED = 'contract_acceptance_started',
78
+ CONTRACT_ACCEPTANCE_COMPLETED = 'contract_acceptance_completed',
79
+ DOCUMENT_SCAN_STARTED = 'identity_document_scan_started',
80
+ DOCUMENT_SCAN_COMPLETED = 'identity_document_scan_completed',
81
+ IDENTITY_DOCUMENT_EID_SCAN_STARTED = 'identity_document_eid_scan_started',
82
+ IDENTITY_DOCUMENT_EID_SCAN_COMPLETED = 'identity_document_eid_scan_completed',
83
+ LIVENESS_CHECK_STARTED = 'liveness_check_started',
84
+ LIVENESS_CHECK_COMPLETED = 'liveness_check_completed',
85
+
86
+ // NFC Scan Events (used by trackNFCScan* helpers)
87
+ NFC_SCAN_STARTED = 'nfc_scan_started',
88
+ NFC_SCAN_COMPLETED = 'nfc_scan_completed',
89
+
90
+ // NFC Error Events (specific error types for NFC failures)
91
+ NFC_SCAN_FAILED = 'nfc_scan_failed',
92
+ NFC_DEVICE_UNSUPPORTED = 'nfc_device_unsupported',
93
+ NFC_READING_ERROR = 'nfc_reading_error',
94
+ NFC_USER_CANCELLED = 'nfc_user_cancelled',
95
+
96
+ // EID Error Events
97
+ IDENTITY_DOCUMENT_EID_SCAN_FAILED = 'identity_document_eid_scan_failed',
98
+
99
+ // Performance Events
100
+ API_CALL_DURATION = 'api_call_duration',
101
+
102
+ // Verification Outcome Events
103
+ VERIFICATION_SUCCESS = 'verification_success',
104
+ VERIFICATION_FAILED = 'verification_failed',
105
+ }
106
+
107
+ /**
108
+ * Device information (anonymized and privacy-compliant)
109
+ */
110
+ export interface AnonymizedDeviceInfo {
111
+ platform: 'ios' | 'android';
112
+ osVersion: string;
113
+ appVersion: string;
114
+ sdkVersion: string;
115
+ locale: string;
116
+ timezone: string;
117
+ screenResolution: string;
118
+ // No device IDs, MAC addresses, or other PII
119
+ }
120
+
121
+ /**
122
+ * Analytics event metadata
123
+ */
124
+ export interface AnalyticsEventMetadata {
125
+ [key: string]: string | number | boolean | null | undefined;
126
+ }
127
+
128
+ /**
129
+ * Base analytics event structure
130
+ */
131
+ export interface AnalyticsEvent {
132
+ eventId: string; // UUID v4
133
+ verificationSessionId: string; // Verification session ID from backend
134
+ eventName: string;
135
+ category: AnalyticsEventCategory;
136
+ timestamp: string; // ISO 8601 format
137
+ deviceInfo: AnonymizedDeviceInfo;
138
+ metadata?: AnalyticsEventMetadata;
139
+ // No PII fields allowed
140
+ }
141
+
142
+ /**
143
+ * Analytics configuration
144
+ */
145
+ export interface AnalyticsConfig {
146
+ enabled: boolean;
147
+ baseUrl: string;
148
+ verificationSessionId: string; // Required: Verification session ID from backend
149
+ }
150
+
151
+ /**
152
+ * Analytics service interface
153
+ */
154
+ export interface IAnalyticsService {
155
+ initialize(config: AnalyticsConfig): Promise<void>;
156
+ trackEvent(
157
+ eventName: string,
158
+ category: AnalyticsEventCategory,
159
+ metadata?: AnalyticsEventMetadata
160
+ ): Promise<void>;
161
+ clear(): Promise<void>;
162
+ isEnabled(): boolean;
163
+ }
164
+
165
+ /**
166
+ * Enhanced error event metadata
167
+ */
168
+ export interface ErrorEventMetadata extends AnalyticsEventMetadata {
169
+ errorCode: string;
170
+ errorMessage: string;
171
+ screenName: string;
172
+ severity: ErrorSeverity;
173
+ category?: ErrorCategory;
174
+ component?: string;
175
+ userAction?: string;
176
+ recoverable?: boolean;
177
+ retryCount?: number;
178
+ stackTrace?: string;
179
+ }
@@ -23,6 +23,7 @@ export const useCameraPermissions = (): UseCameraPermissionResult => {
23
23
  const permission = await requestCameraPermission();
24
24
 
25
25
  if (!permission) {
26
+ // Camera permission denied by user - their choice, not actionable
26
27
  Alert.alert(
27
28
  'Camera Permission Required',
28
29
  'This app needs camera access to scan barcodes. Please enable camera permissions in your device settings.',
@@ -62,7 +62,8 @@ export default {
62
62
  'livenessDetectionScreen.guideHeader': 'Yüz Taraması İçin Hazırlanın',
63
63
  'livenessDetectionScreen.guideText':
64
64
  'Başlamadan önce lütfen aşağıdaki hususlara dikkat edin:',
65
- 'livenessDetectionScreen.guidePoint1': 'Gözlük, şapka veya eşarp takmayın. Lütfen uygun şekilde giyindiğinizden emin olun',
65
+ 'livenessDetectionScreen.guidePoint1':
66
+ 'Gözlük, şapka veya eşarp takmayın. Lütfen uygun şekilde giyindiğinizden emin olun',
66
67
  'livenessDetectionScreen.guidePoint2':
67
68
  'Yüzünüzün iyi aydınlatıldığından emin olun',
68
69
  'livenessDetectionScreen.guidePoint3': 'Arka plan gürültüsünü minimize edin',
@@ -2,6 +2,8 @@ import i18n from 'i18next';
2
2
  import { initReactI18next } from 'react-i18next';
3
3
  import * as resources from './Resources';
4
4
 
5
+ import { trackError } from '../Shared/Libs/analytics.utils';
6
+
5
7
  const getCurrentLanguage = () => {
6
8
  try {
7
9
  if (typeof Intl !== 'undefined' && Intl.DateTimeFormat) {
@@ -15,6 +17,13 @@ const getCurrentLanguage = () => {
15
17
  return 'en';
16
18
  } catch (error) {
17
19
  console.error('Error detecting language:', error);
20
+ trackError(
21
+ 'LANGUAGE_DETECTION_ERROR',
22
+ error instanceof Error ? error.message : 'Unknown error',
23
+ 'translation_init',
24
+ 'low',
25
+ { recoverable: true, userAction: 'detect_language' }
26
+ ).catch(() => { });
18
27
  return 'en';
19
28
  }
20
29
  };