@trustchex/react-native-sdk 1.250.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 (100) 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 +82 -72
  13. package/lib/module/Shared/Components/EIDScanner.js +63 -3
  14. package/lib/module/Shared/Components/FaceCamera.js +73 -6
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.js +9 -4
  16. package/lib/module/Shared/Components/LanguageSelector.js +14 -10
  17. package/lib/module/Shared/Components/NavigationManager.js +4 -2
  18. package/lib/module/Shared/Components/QrCodeScannerCamera.js +6 -1
  19. package/lib/module/Shared/Components/StyledButton.js +108 -9
  20. package/lib/module/Shared/Components/StyledTextInput.js +87 -0
  21. package/lib/module/Shared/Contexts/AppContext.js +3 -1
  22. package/lib/module/Shared/Contexts/ThemeContext.js +40 -0
  23. package/lib/module/Shared/Libs/analytics.utils.js +430 -0
  24. package/lib/module/Shared/Libs/camera.utils.js +58 -2
  25. package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
  26. package/lib/module/Shared/Libs/http-client.js +89 -28
  27. package/lib/module/Shared/Services/AnalyticsService.js +404 -0
  28. package/lib/module/Shared/Types/analytics.types.js +111 -0
  29. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
  30. package/lib/module/Translation/index.js +17 -5
  31. package/lib/module/Trustchex.js +52 -16
  32. package/lib/module/index.js +3 -0
  33. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  35. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  36. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  37. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  38. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  39. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
  42. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  43. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  44. package/lib/typescript/src/Shared/Components/LanguageSelector.d.ts.map +1 -1
  45. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  46. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/Components/StyledButton.d.ts +12 -2
  48. package/lib/typescript/src/Shared/Components/StyledButton.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts +15 -0
  50. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts.map +1 -0
  51. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  52. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  53. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts +26 -0
  54. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts.map +1 -0
  55. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
  56. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
  57. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
  58. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
  62. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
  63. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
  64. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
  66. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  67. package/lib/typescript/src/Translation/index.d.ts.map +1 -1
  68. package/lib/typescript/src/Trustchex.d.ts +1 -0
  69. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  70. package/lib/typescript/src/index.d.ts +4 -0
  71. package/lib/typescript/src/index.d.ts.map +1 -1
  72. package/package.json +6 -7
  73. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
  74. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
  75. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
  76. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
  77. package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
  78. package/src/Screens/Static/ResultScreen.tsx +79 -4
  79. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +113 -90
  80. package/src/Shared/Components/EIDScanner.tsx +132 -3
  81. package/src/Shared/Components/FaceCamera.tsx +81 -4
  82. package/src/Shared/Components/IdentityDocumentCamera.tsx +8 -6
  83. package/src/Shared/Components/LanguageSelector.tsx +12 -11
  84. package/src/Shared/Components/NavigationManager.tsx +5 -3
  85. package/src/Shared/Components/QrCodeScannerCamera.tsx +5 -1
  86. package/src/Shared/Components/StyledButton.tsx +141 -10
  87. package/src/Shared/Components/StyledTextInput.tsx +128 -0
  88. package/src/Shared/Contexts/AppContext.ts +4 -0
  89. package/src/Shared/Contexts/ThemeContext.tsx +67 -0
  90. package/src/Shared/Libs/analytics.utils.ts +644 -0
  91. package/src/Shared/Libs/camera.utils.ts +74 -2
  92. package/src/Shared/Libs/deeplink.utils.ts +5 -0
  93. package/src/Shared/Libs/http-client.ts +105 -31
  94. package/src/Shared/Services/AnalyticsService.ts +470 -0
  95. package/src/Shared/Types/analytics.types.ts +179 -0
  96. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
  97. package/src/Translation/Resources/tr.ts +2 -1
  98. package/src/Translation/index.ts +21 -10
  99. package/src/Trustchex.tsx +65 -20
  100. package/src/index.tsx +33 -0
@@ -0,0 +1,67 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+
3
+ export interface ThemeColors {
4
+ primary: string;
5
+ secondary: string;
6
+ tertiary: string;
7
+ background: string;
8
+ surface: string;
9
+ text: string;
10
+ textSecondary: string;
11
+ border: string;
12
+ error: string;
13
+ success: string;
14
+ }
15
+
16
+ interface ThemeContextType {
17
+ colors: ThemeColors;
18
+ }
19
+
20
+ const defaultColors: ThemeColors = {
21
+ primary: '#000000',
22
+ secondary: '#CCCCCC',
23
+ tertiary: '#FF0000',
24
+ background: '#FFFFFF',
25
+ surface: '#F5F5F5',
26
+ text: '#000000',
27
+ textSecondary: '#666666',
28
+ border: '#DDDDDD',
29
+ error: '#E53935',
30
+ success: '#43A047',
31
+ };
32
+
33
+ const ThemeContext = createContext<ThemeContextType>({
34
+ colors: defaultColors,
35
+ });
36
+
37
+ export const useTheme = () => useContext(ThemeContext);
38
+
39
+ interface ThemeProviderProps {
40
+ children: React.ReactNode;
41
+ primaryColor?: string;
42
+ secondaryColor?: string;
43
+ tertiaryColor?: string;
44
+ }
45
+
46
+ export const ThemeProvider: React.FC<ThemeProviderProps> = ({
47
+ children,
48
+ primaryColor,
49
+ secondaryColor,
50
+ tertiaryColor,
51
+ }) => {
52
+ const colors = useMemo(
53
+ () => ({
54
+ ...defaultColors,
55
+ primary: primaryColor || defaultColors.primary,
56
+ secondary: secondaryColor || defaultColors.secondary,
57
+ tertiary: tertiaryColor || defaultColors.tertiary,
58
+ }),
59
+ [primaryColor, secondaryColor, tertiaryColor]
60
+ );
61
+
62
+ const value = useMemo(() => ({ colors }), [colors]);
63
+
64
+ return (
65
+ <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>
66
+ );
67
+ };
@@ -0,0 +1,644 @@
1
+ /**
2
+ * Analytics Helper Utilities
3
+ * Convenience functions for common analytics tracking patterns
4
+ * All functions handle errors silently to prevent blocking application flow
5
+ */
6
+
7
+ import { useEffect, useState } from 'react';
8
+ import { analyticsService } from '../Services/AnalyticsService';
9
+ import {
10
+ AnalyticsEventCategory,
11
+ AnalyticsEventName,
12
+ } from '../Types/analytics.types';
13
+ import type { AnalyticsEventMetadata, ErrorSeverity, ErrorContext } from '../Types/analytics.types';
14
+
15
+ /**
16
+ * Track screen view event
17
+ */
18
+ export const trackScreenView = async (
19
+ screenName: string,
20
+ previousScreen?: string
21
+ ) => {
22
+ try {
23
+ await analyticsService.trackEvent(
24
+ AnalyticsEventName.SCREEN_VIEW,
25
+ AnalyticsEventCategory.NAVIGATION,
26
+ {
27
+ screenName,
28
+ previousScreen,
29
+ }
30
+ );
31
+ } catch (error) {
32
+ // Silently fail - analytics should never block app flow
33
+ }
34
+ };
35
+
36
+ /**
37
+ * Track screen exit with duration
38
+ */
39
+ export const trackScreenExit = async (
40
+ screenName: string,
41
+ durationMs: number
42
+ ) => {
43
+ try {
44
+ await analyticsService.trackEvent(
45
+ AnalyticsEventName.SCREEN_EXIT,
46
+ AnalyticsEventCategory.NAVIGATION,
47
+ {
48
+ screenName,
49
+ duration: durationMs,
50
+ }
51
+ );
52
+ } catch (error) {
53
+ // Silently fail - analytics should never block app flow
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Track button click
59
+ */
60
+ export const trackButtonClick = async (
61
+ buttonName: string,
62
+ screenName: string,
63
+ metadata?: AnalyticsEventMetadata
64
+ ) => {
65
+ try {
66
+ await analyticsService.trackEvent(
67
+ AnalyticsEventName.BUTTON_CLICK,
68
+ AnalyticsEventCategory.USER_ACTION,
69
+ {
70
+ buttonName,
71
+ screenName,
72
+ ...metadata,
73
+ }
74
+ );
75
+ } catch (error) {
76
+ // Silently fail - analytics should never block app flow
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Sanitize stack trace to remove PII and file paths
82
+ */
83
+ const sanitizeStackTrace = (error: Error): string | undefined => {
84
+ if (!error.stack) return undefined;
85
+ try {
86
+ // Keep only function names and line numbers, remove file paths
87
+ const lines = error.stack.split('\n').slice(0, 5);
88
+ return lines
89
+ .map(line => {
90
+ // Remove full file paths, keep just the filename and line
91
+ return line.replace(/\(.*\/([^/]+:[0-9]+:[0-9]+)\)/, '($1)')
92
+ .replace(/at\s+.*\/([^/]+)/, 'at $1')
93
+ .trim();
94
+ })
95
+ .join(' -> ');
96
+ } catch {
97
+ return undefined;
98
+ }
99
+ };
100
+
101
+ /**
102
+ * Infer error category from error code
103
+ */
104
+ const inferErrorCategory = (errorCode: string): string => {
105
+ const code = errorCode.toLowerCase();
106
+ if (code.includes('network') || code.includes('fetch') || code.includes('timeout')) return 'network';
107
+ if (code.includes('permission') || code.includes('denied')) return 'permission';
108
+ if (code.includes('camera')) return 'camera';
109
+ if (code.includes('nfc') || code.includes('eid')) return 'nfc';
110
+ if (code.includes('validation') || code.includes('invalid')) return 'validation';
111
+ if (code.includes('api') || code.includes('response') || code.includes('status')) return 'api';
112
+ if (code.includes('storage') || code.includes('file') || code.includes('save')) return 'storage';
113
+ if (code.includes('device')) return 'device';
114
+ if (code.includes('input') || code.includes('user')) return 'user_input';
115
+ return 'unknown';
116
+ };
117
+
118
+ /**
119
+ * Track error event with unique event name per error code
120
+ * Event name format: error_{errorCode} (e.g., error_camera_permission_denied)
121
+ *
122
+ * Only errors with severity 'medium' or higher are recorded.
123
+ * Low severity errors are ignored to reduce noise.
124
+ *
125
+ * @param errorCode - Unique identifier for the error type
126
+ * @param errorMessage - Human-readable error message (sanitized)
127
+ * @param screenName - Screen where the error occurred
128
+ * @param severity - Error severity level: 'low' | 'medium' | 'high' | 'critical'
129
+ * @param context - Optional additional context for the error
130
+ */
131
+ export const trackError = async (
132
+ errorCode: string,
133
+ errorMessage: string,
134
+ screenName: string,
135
+ severity: 'low' | 'medium' | 'high' | 'critical' = 'medium',
136
+ context?: {
137
+ error?: Error;
138
+ component?: string;
139
+ userAction?: string;
140
+ recoverable?: boolean;
141
+ retryCount?: number;
142
+ category?: string;
143
+ additionalData?: Record<string, string | number | boolean | null>;
144
+ }
145
+ ) => {
146
+ try {
147
+ // Only track errors with severity medium or higher
148
+ // Low severity errors are expected/recoverable and create noise
149
+ if (severity === 'low') {
150
+ return;
151
+ }
152
+
153
+ // Create unique event name per error code
154
+ const eventName = `error_${errorCode.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
155
+
156
+ // Build metadata with all available context
157
+ const metadata: AnalyticsEventMetadata = {
158
+ errorCode,
159
+ errorMessage,
160
+ screenName,
161
+ severity,
162
+ category: context?.category || inferErrorCategory(errorCode),
163
+ };
164
+
165
+ // Add optional context fields
166
+ if (context?.component) metadata.component = context.component;
167
+ if (context?.userAction) metadata.userAction = context.userAction;
168
+ if (context?.recoverable !== undefined) metadata.recoverable = context.recoverable;
169
+ if (context?.retryCount !== undefined) metadata.retryCount = context.retryCount;
170
+
171
+ // Add sanitized stack trace if error object provided
172
+ if (context?.error) {
173
+ const stackTrace = sanitizeStackTrace(context.error);
174
+ if (stackTrace) metadata.stackTrace = stackTrace;
175
+ }
176
+
177
+ // Merge additional data
178
+ if (context?.additionalData) {
179
+ Object.entries(context.additionalData).forEach(([key, value]) => {
180
+ metadata[key] = value;
181
+ });
182
+ }
183
+
184
+ await analyticsService.trackEvent(
185
+ eventName,
186
+ AnalyticsEventCategory.ERROR,
187
+ metadata
188
+ );
189
+ } catch (error) {
190
+ // Silently fail - analytics should never block app flow
191
+ }
192
+ };
193
+
194
+ /**
195
+ * Track error with full Error object for automatic stack trace extraction
196
+ */
197
+ export const trackErrorWithDetails = async (
198
+ error: Error,
199
+ errorCode: string,
200
+ screenName: string,
201
+ severity: 'low' | 'medium' | 'high' | 'critical' = 'medium',
202
+ context?: {
203
+ component?: string;
204
+ userAction?: string;
205
+ recoverable?: boolean;
206
+ retryCount?: number;
207
+ category?: string;
208
+ additionalData?: Record<string, string | number | boolean | null>;
209
+ }
210
+ ) => {
211
+ return trackError(
212
+ errorCode,
213
+ error.message || 'Unknown error',
214
+ screenName,
215
+ severity,
216
+ { ...context, error }
217
+ );
218
+ };
219
+
220
+ /**
221
+ * Track API call performance
222
+ */
223
+ export const trackApiCall = async (
224
+ endpoint: string,
225
+ durationMs: number,
226
+ statusCode: number,
227
+ success: boolean
228
+ ) => {
229
+ try {
230
+ await analyticsService.trackEvent(
231
+ AnalyticsEventName.API_CALL_DURATION,
232
+ AnalyticsEventCategory.PERFORMANCE,
233
+ {
234
+ endpoint,
235
+ duration: durationMs,
236
+ statusCode,
237
+ success,
238
+ }
239
+ );
240
+ } catch (error) {
241
+ // Silently fail - analytics should never block app flow
242
+ }
243
+ };
244
+
245
+ /**
246
+ * Map workflow step types to valid analytics event names
247
+ */
248
+ const STEP_EVENT_MAP: Record<string, { started: AnalyticsEventName; completed: AnalyticsEventName }> = {
249
+ contract_acceptance: {
250
+ started: AnalyticsEventName.CONTRACT_ACCEPTANCE_STARTED,
251
+ completed: AnalyticsEventName.CONTRACT_ACCEPTANCE_COMPLETED,
252
+ },
253
+ identity_document_scan: {
254
+ started: AnalyticsEventName.DOCUMENT_SCAN_STARTED,
255
+ completed: AnalyticsEventName.DOCUMENT_SCAN_COMPLETED,
256
+ },
257
+ identity_document_eid_scan: {
258
+ started: AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_STARTED,
259
+ completed: AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_COMPLETED,
260
+ },
261
+ liveness_check: {
262
+ started: AnalyticsEventName.LIVENESS_CHECK_STARTED,
263
+ completed: AnalyticsEventName.LIVENESS_CHECK_COMPLETED,
264
+ },
265
+ };
266
+
267
+ function getStepEventName(stepType: string, suffix: 'started' | 'completed'): string {
268
+ const normalizedStep = stepType.toLowerCase();
269
+ const mapping = STEP_EVENT_MAP[normalizedStep];
270
+ if (mapping) {
271
+ return mapping[suffix];
272
+ }
273
+ // Fallback to dynamic name for unknown step types
274
+ return `${normalizedStep}_${suffix}`;
275
+ }
276
+
277
+ /**
278
+ * Track verification process events
279
+ */
280
+ export const trackVerificationStart = async (
281
+ stepType: string
282
+ ) => {
283
+ try {
284
+ const eventName = getStepEventName(stepType, 'started');
285
+
286
+ await analyticsService.trackEvent(
287
+ eventName,
288
+ AnalyticsEventCategory.VERIFICATION,
289
+ {
290
+ stepType,
291
+ }
292
+ );
293
+ } catch (error) {
294
+ // Silently fail - analytics should never block app flow
295
+ }
296
+ };
297
+
298
+ export const trackVerificationComplete = async (
299
+ stepType: string,
300
+ success: boolean,
301
+ attemptNumber?: number
302
+ ) => {
303
+ try {
304
+ const eventName = getStepEventName(stepType, 'completed');
305
+
306
+ await analyticsService.trackEvent(
307
+ eventName,
308
+ AnalyticsEventCategory.VERIFICATION,
309
+ {
310
+ stepType,
311
+ success,
312
+ attemptNumber,
313
+ }
314
+ );
315
+ } catch (error) {
316
+ // Silently fail - analytics should never block app flow
317
+ }
318
+ };
319
+
320
+ /**
321
+ * Track session events
322
+ */
323
+ export const trackSessionStart = async (sessionId: string) => {
324
+ try {
325
+ await analyticsService.trackEvent(
326
+ AnalyticsEventName.SESSION_START,
327
+ AnalyticsEventCategory.SESSION,
328
+ {
329
+ sessionId,
330
+ }
331
+ );
332
+ } catch (error) {
333
+ // Silently fail - analytics should never block app flow
334
+ }
335
+ };
336
+
337
+ export const trackSessionEnd = async (sessionId: string, reason?: string) => {
338
+ try {
339
+ await analyticsService.trackEvent(
340
+ AnalyticsEventName.SESSION_END,
341
+ AnalyticsEventCategory.SESSION,
342
+ {
343
+ sessionId,
344
+ reason,
345
+ }
346
+ );
347
+ } catch (error) {
348
+ // Silently fail - analytics should never block app flow
349
+ }
350
+ };
351
+
352
+ /**
353
+ * Track NFC scan events with detailed metadata
354
+ */
355
+ export const trackNFCScanStart = async (
356
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
357
+ nfcSupported: boolean,
358
+ nfcEnabled: boolean,
359
+ attemptNumber: number = 1
360
+ ) => {
361
+ try {
362
+ await analyticsService.trackEvent(
363
+ AnalyticsEventName.NFC_SCAN_STARTED,
364
+ AnalyticsEventCategory.VERIFICATION,
365
+ {
366
+ verificationType: 'nfc',
367
+ documentType,
368
+ nfcSupported,
369
+ nfcEnabled,
370
+ attemptNumber,
371
+ }
372
+ );
373
+ } catch (error) {
374
+ // Silently fail - analytics should never block app flow
375
+ }
376
+ };
377
+
378
+ export const trackNFCScanComplete = async (
379
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
380
+ scanDuration: number,
381
+ attemptNumber: number = 1
382
+ ) => {
383
+ try {
384
+ await analyticsService.trackEvent(
385
+ AnalyticsEventName.NFC_SCAN_COMPLETED,
386
+ AnalyticsEventCategory.VERIFICATION,
387
+ {
388
+ verificationType: 'nfc',
389
+ success: true,
390
+ documentType,
391
+ scanDuration,
392
+ attemptNumber,
393
+ }
394
+ );
395
+ } catch (error) {
396
+ // Silently fail - analytics should never block app flow
397
+ }
398
+ };
399
+
400
+ export const trackNFCScanFailed = async (
401
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
402
+ errorType:
403
+ | 'device_unsupported'
404
+ | 'not_enabled'
405
+ | 'reading_error'
406
+ | 'user_cancelled'
407
+ | 'timeout'
408
+ | 'unknown',
409
+ errorMessage: string,
410
+ scanDuration: number,
411
+ attemptNumber: number = 1,
412
+ deviceModel?: string,
413
+ osVersion?: string
414
+ ) => {
415
+ try {
416
+ const eventName =
417
+ errorType === 'device_unsupported'
418
+ ? AnalyticsEventName.NFC_DEVICE_UNSUPPORTED
419
+ : errorType === 'user_cancelled'
420
+ ? AnalyticsEventName.NFC_USER_CANCELLED
421
+ : errorType === 'reading_error'
422
+ ? AnalyticsEventName.NFC_READING_ERROR
423
+ : AnalyticsEventName.NFC_SCAN_FAILED;
424
+
425
+ await analyticsService.trackEvent(eventName, AnalyticsEventCategory.ERROR, {
426
+ verificationType: 'nfc',
427
+ success: false,
428
+ documentType,
429
+ errorType,
430
+ errorMessage,
431
+ scanDuration,
432
+ attemptNumber,
433
+ deviceModel,
434
+ osVersion,
435
+ });
436
+ } catch (error) {
437
+ // Silently fail - analytics should never block app flow
438
+ }
439
+ };
440
+
441
+ /**
442
+ * EID (identity document with NFC) specific events
443
+ */
444
+ export const trackEIDScanStart = async (
445
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
446
+ nfcSupported: boolean,
447
+ nfcEnabled: boolean,
448
+ attemptNumber: number = 1
449
+ ) => {
450
+ try {
451
+ await analyticsService.trackEvent(
452
+ AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_STARTED,
453
+ AnalyticsEventCategory.VERIFICATION,
454
+ {
455
+ verificationType: 'nfc',
456
+ documentType,
457
+ nfcSupported,
458
+ nfcEnabled,
459
+ attemptNumber,
460
+ }
461
+ );
462
+ } catch (error) {
463
+ // Silently fail - analytics should never block app flow
464
+ }
465
+ };
466
+
467
+ export const trackEIDScanComplete = async (
468
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
469
+ scanDuration: number,
470
+ attemptNumber: number = 1
471
+ ) => {
472
+ try {
473
+ await analyticsService.trackEvent(
474
+ AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_COMPLETED,
475
+ AnalyticsEventCategory.VERIFICATION,
476
+ {
477
+ verificationType: 'nfc',
478
+ success: true,
479
+ documentType,
480
+ scanDuration,
481
+ attemptNumber,
482
+ }
483
+ );
484
+ } catch (error) {
485
+ // Silently fail - analytics should never block app flow
486
+ }
487
+ };
488
+
489
+ export const trackEIDScanFailed = async (
490
+ documentType: 'ID' | 'PASSPORT' | 'UNKNOWN',
491
+ errorType:
492
+ | 'device_unsupported'
493
+ | 'not_enabled'
494
+ | 'reading_error'
495
+ | 'user_cancelled'
496
+ | 'timeout'
497
+ | 'unknown',
498
+ errorMessage: string,
499
+ scanDuration: number,
500
+ attemptNumber: number = 1,
501
+ deviceModel?: string,
502
+ osVersion?: string
503
+ ) => {
504
+ try {
505
+ await analyticsService.trackEvent(
506
+ AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_FAILED,
507
+ AnalyticsEventCategory.ERROR,
508
+ {
509
+ verificationType: 'nfc',
510
+ success: false,
511
+ documentType,
512
+ errorType,
513
+ errorMessage,
514
+ scanDuration,
515
+ attemptNumber,
516
+ deviceModel,
517
+ osVersion,
518
+ }
519
+ );
520
+ } catch (error) {
521
+ // Silently fail - analytics should never block app flow
522
+ }
523
+ };
524
+
525
+ /**
526
+ * Track step abandonment
527
+ */
528
+ export const trackStepAbandoned = async (
529
+ stepName: string,
530
+ stepIndex: number,
531
+ totalSteps: number,
532
+ timeOnStep: number
533
+ ) => {
534
+ try {
535
+ await analyticsService.trackEvent(
536
+ AnalyticsEventName.STEP_ABANDONED,
537
+ AnalyticsEventCategory.USER_ACTION,
538
+ {
539
+ stepName,
540
+ stepIndex,
541
+ totalSteps,
542
+ timeOnStep,
543
+ isRequired: true,
544
+ }
545
+ );
546
+ } catch (error) {
547
+ // Silently fail - analytics should never block app flow
548
+ }
549
+ };
550
+
551
+ /**
552
+ * Track step skipped
553
+ */
554
+ export const trackStepSkipped = async (
555
+ stepName: string,
556
+ stepIndex: number,
557
+ totalSteps: number,
558
+ previousStep?: string
559
+ ) => {
560
+ try {
561
+ await analyticsService.trackEvent(
562
+ AnalyticsEventName.STEP_SKIPPED,
563
+ AnalyticsEventCategory.USER_ACTION,
564
+ {
565
+ stepName,
566
+ stepIndex,
567
+ totalSteps,
568
+ previousStep,
569
+ isRequired: false,
570
+ }
571
+ );
572
+ } catch (error) {
573
+ // Silently fail - analytics should never block app flow
574
+ }
575
+ };
576
+
577
+ /**
578
+ * Track consent/contract acceptance
579
+ */
580
+ export const trackConsentGiven = async (
581
+ contractIds: string[],
582
+ screenName: string = 'contract_acceptance'
583
+ ) => {
584
+ try {
585
+ await analyticsService.trackEvent(
586
+ AnalyticsEventName.CONSENT_GIVEN,
587
+ AnalyticsEventCategory.USER_ACTION,
588
+ {
589
+ screenName,
590
+ contractIds: contractIds.join(','),
591
+ consentType: 'contract_acceptance',
592
+ }
593
+ );
594
+ } catch (error) {
595
+ // Silently fail - analytics should never block app flow
596
+ }
597
+ };
598
+
599
+ /**
600
+ * Track funnel step progress
601
+ */
602
+ export const trackFunnelStep = async (
603
+ stepName: string,
604
+ stepIndex: number,
605
+ totalSteps: number,
606
+ previousStep?: string,
607
+ isRequired: boolean = true
608
+ ) => {
609
+ try {
610
+ await analyticsService.trackEvent(
611
+ AnalyticsEventName.SCREEN_VIEW,
612
+ AnalyticsEventCategory.NAVIGATION,
613
+ {
614
+ screenName: stepName,
615
+ stepName,
616
+ stepIndex,
617
+ totalSteps,
618
+ previousStep,
619
+ isRequired,
620
+ }
621
+ );
622
+ } catch (error) {
623
+ // Silently fail - analytics should never block app flow
624
+ }
625
+ };
626
+
627
+ /**
628
+ * React hook for screen tracking
629
+ */
630
+ export const useScreenTracking = (screenName: string) => {
631
+ const [screenStartTime] = useState(Date.now());
632
+
633
+ useEffect(() => {
634
+ // Track screen view on mount
635
+ trackScreenView(screenName);
636
+
637
+ // Track screen exit on unmount with current screenName captured in closure
638
+ const currentScreen = screenName;
639
+ return () => {
640
+ const duration = Date.now() - screenStartTime;
641
+ trackScreenExit(currentScreen, duration);
642
+ };
643
+ }, [screenName, screenStartTime]);
644
+ };