@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.
- package/README.md +43 -2
- package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
- package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
- package/ios/DeviceBrightnessModule.h +4 -0
- package/ios/DeviceBrightnessModule.m +27 -0
- package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
- package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
- package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
- package/lib/module/Screens/Static/ResultScreen.js +52 -3
- package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +41 -2
- package/lib/module/Shared/Components/EIDScanner.js +63 -3
- package/lib/module/Shared/Components/FaceCamera.js +69 -4
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +4 -1
- package/lib/module/Shared/Components/NavigationManager.js +2 -0
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +2 -0
- package/lib/module/Shared/Contexts/AppContext.js +3 -1
- package/lib/module/Shared/Libs/analytics.utils.js +430 -0
- package/lib/module/Shared/Libs/camera.utils.js +58 -2
- package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
- package/lib/module/Shared/Libs/http-client.js +89 -28
- package/lib/module/Shared/Services/AnalyticsService.js +404 -0
- package/lib/module/Shared/Types/analytics.types.js +111 -0
- package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
- package/lib/module/Translation/index.js +5 -0
- package/lib/module/Trustchex.js +47 -4
- package/lib/module/index.js +3 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
- package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Translation/index.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +1 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +6 -2
- package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
- package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
- package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
- package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
- package/src/Screens/Static/ResultScreen.tsx +79 -4
- package/src/Screens/Static/VerificationSessionCheckScreen.tsx +65 -10
- package/src/Shared/Components/EIDScanner.tsx +132 -3
- package/src/Shared/Components/FaceCamera.tsx +77 -2
- package/src/Shared/Components/IdentityDocumentCamera.tsx +4 -4
- package/src/Shared/Components/NavigationManager.tsx +2 -0
- package/src/Shared/Components/QrCodeScannerCamera.tsx +2 -0
- package/src/Shared/Contexts/AppContext.ts +4 -0
- package/src/Shared/Libs/analytics.utils.ts +644 -0
- package/src/Shared/Libs/camera.utils.ts +74 -2
- package/src/Shared/Libs/deeplink.utils.ts +5 -0
- package/src/Shared/Libs/http-client.ts +105 -31
- package/src/Shared/Services/AnalyticsService.ts +470 -0
- package/src/Shared/Types/analytics.types.ts +179 -0
- package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
- package/src/Translation/Resources/tr.ts +2 -1
- package/src/Translation/index.ts +9 -0
- package/src/Trustchex.tsx +54 -2
- package/src/index.tsx +33 -0
|
@@ -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
|
+
};
|