@trustchex/react-native-sdk 1.253.0 → 1.266.1
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/module/version.js +5 -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/lib/typescript/src/version.d.ts +2 -0
- package/lib/typescript/src/version.d.ts.map +1 -0
- 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
- package/src/version.ts +3 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Analytics Helper Utilities
|
|
5
|
+
* Convenience functions for common analytics tracking patterns
|
|
6
|
+
* All functions handle errors silently to prevent blocking application flow
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect, useState } from 'react';
|
|
10
|
+
import { analyticsService } from "../Services/AnalyticsService.js";
|
|
11
|
+
import { AnalyticsEventCategory, AnalyticsEventName } from "../Types/analytics.types.js";
|
|
12
|
+
/**
|
|
13
|
+
* Track screen view event
|
|
14
|
+
*/
|
|
15
|
+
export const trackScreenView = async (screenName, previousScreen) => {
|
|
16
|
+
try {
|
|
17
|
+
await analyticsService.trackEvent(AnalyticsEventName.SCREEN_VIEW, AnalyticsEventCategory.NAVIGATION, {
|
|
18
|
+
screenName,
|
|
19
|
+
previousScreen
|
|
20
|
+
});
|
|
21
|
+
} catch (error) {
|
|
22
|
+
// Silently fail - analytics should never block app flow
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Track screen exit with duration
|
|
28
|
+
*/
|
|
29
|
+
export const trackScreenExit = async (screenName, durationMs) => {
|
|
30
|
+
try {
|
|
31
|
+
await analyticsService.trackEvent(AnalyticsEventName.SCREEN_EXIT, AnalyticsEventCategory.NAVIGATION, {
|
|
32
|
+
screenName,
|
|
33
|
+
duration: durationMs
|
|
34
|
+
});
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// Silently fail - analytics should never block app flow
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Track button click
|
|
42
|
+
*/
|
|
43
|
+
export const trackButtonClick = async (buttonName, screenName, metadata) => {
|
|
44
|
+
try {
|
|
45
|
+
await analyticsService.trackEvent(AnalyticsEventName.BUTTON_CLICK, AnalyticsEventCategory.USER_ACTION, {
|
|
46
|
+
buttonName,
|
|
47
|
+
screenName,
|
|
48
|
+
...metadata
|
|
49
|
+
});
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// Silently fail - analytics should never block app flow
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Sanitize stack trace to remove PII and file paths
|
|
57
|
+
*/
|
|
58
|
+
const sanitizeStackTrace = error => {
|
|
59
|
+
if (!error.stack) return undefined;
|
|
60
|
+
try {
|
|
61
|
+
// Keep only function names and line numbers, remove file paths
|
|
62
|
+
const lines = error.stack.split('\n').slice(0, 5);
|
|
63
|
+
return lines.map(line => {
|
|
64
|
+
// Remove full file paths, keep just the filename and line
|
|
65
|
+
return line.replace(/\(.*\/([^/]+:[0-9]+:[0-9]+)\)/, '($1)').replace(/at\s+.*\/([^/]+)/, 'at $1').trim();
|
|
66
|
+
}).join(' -> ');
|
|
67
|
+
} catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Infer error category from error code
|
|
74
|
+
*/
|
|
75
|
+
const inferErrorCategory = errorCode => {
|
|
76
|
+
const code = errorCode.toLowerCase();
|
|
77
|
+
if (code.includes('network') || code.includes('fetch') || code.includes('timeout')) return 'network';
|
|
78
|
+
if (code.includes('permission') || code.includes('denied')) return 'permission';
|
|
79
|
+
if (code.includes('camera')) return 'camera';
|
|
80
|
+
if (code.includes('nfc') || code.includes('eid')) return 'nfc';
|
|
81
|
+
if (code.includes('validation') || code.includes('invalid')) return 'validation';
|
|
82
|
+
if (code.includes('api') || code.includes('response') || code.includes('status')) return 'api';
|
|
83
|
+
if (code.includes('storage') || code.includes('file') || code.includes('save')) return 'storage';
|
|
84
|
+
if (code.includes('device')) return 'device';
|
|
85
|
+
if (code.includes('input') || code.includes('user')) return 'user_input';
|
|
86
|
+
return 'unknown';
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Track error event with unique event name per error code
|
|
91
|
+
* Event name format: error_{errorCode} (e.g., error_camera_permission_denied)
|
|
92
|
+
*
|
|
93
|
+
* Only errors with severity 'medium' or higher are recorded.
|
|
94
|
+
* Low severity errors are ignored to reduce noise.
|
|
95
|
+
*
|
|
96
|
+
* @param errorCode - Unique identifier for the error type
|
|
97
|
+
* @param errorMessage - Human-readable error message (sanitized)
|
|
98
|
+
* @param screenName - Screen where the error occurred
|
|
99
|
+
* @param severity - Error severity level: 'low' | 'medium' | 'high' | 'critical'
|
|
100
|
+
* @param context - Optional additional context for the error
|
|
101
|
+
*/
|
|
102
|
+
export const trackError = async (errorCode, errorMessage, screenName, severity = 'medium', context) => {
|
|
103
|
+
try {
|
|
104
|
+
// Only track errors with severity medium or higher
|
|
105
|
+
// Low severity errors are expected/recoverable and create noise
|
|
106
|
+
if (severity === 'low') {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Create unique event name per error code
|
|
111
|
+
const eventName = `error_${errorCode.toLowerCase().replace(/[^a-z0-9]/g, '_')}`;
|
|
112
|
+
|
|
113
|
+
// Build metadata with all available context
|
|
114
|
+
const metadata = {
|
|
115
|
+
errorCode,
|
|
116
|
+
errorMessage,
|
|
117
|
+
screenName,
|
|
118
|
+
severity,
|
|
119
|
+
category: context?.category || inferErrorCategory(errorCode)
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Add optional context fields
|
|
123
|
+
if (context?.component) metadata.component = context.component;
|
|
124
|
+
if (context?.userAction) metadata.userAction = context.userAction;
|
|
125
|
+
if (context?.recoverable !== undefined) metadata.recoverable = context.recoverable;
|
|
126
|
+
if (context?.retryCount !== undefined) metadata.retryCount = context.retryCount;
|
|
127
|
+
|
|
128
|
+
// Add sanitized stack trace if error object provided
|
|
129
|
+
if (context?.error) {
|
|
130
|
+
const stackTrace = sanitizeStackTrace(context.error);
|
|
131
|
+
if (stackTrace) metadata.stackTrace = stackTrace;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Merge additional data
|
|
135
|
+
if (context?.additionalData) {
|
|
136
|
+
Object.entries(context.additionalData).forEach(([key, value]) => {
|
|
137
|
+
metadata[key] = value;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
await analyticsService.trackEvent(eventName, AnalyticsEventCategory.ERROR, metadata);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Silently fail - analytics should never block app flow
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Track error with full Error object for automatic stack trace extraction
|
|
148
|
+
*/
|
|
149
|
+
export const trackErrorWithDetails = async (error, errorCode, screenName, severity = 'medium', context) => {
|
|
150
|
+
return trackError(errorCode, error.message || 'Unknown error', screenName, severity, {
|
|
151
|
+
...context,
|
|
152
|
+
error
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Track API call performance
|
|
158
|
+
*/
|
|
159
|
+
export const trackApiCall = async (endpoint, durationMs, statusCode, success) => {
|
|
160
|
+
try {
|
|
161
|
+
await analyticsService.trackEvent(AnalyticsEventName.API_CALL_DURATION, AnalyticsEventCategory.PERFORMANCE, {
|
|
162
|
+
endpoint,
|
|
163
|
+
duration: durationMs,
|
|
164
|
+
statusCode,
|
|
165
|
+
success
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// Silently fail - analytics should never block app flow
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Map workflow step types to valid analytics event names
|
|
174
|
+
*/
|
|
175
|
+
const STEP_EVENT_MAP = {
|
|
176
|
+
contract_acceptance: {
|
|
177
|
+
started: AnalyticsEventName.CONTRACT_ACCEPTANCE_STARTED,
|
|
178
|
+
completed: AnalyticsEventName.CONTRACT_ACCEPTANCE_COMPLETED
|
|
179
|
+
},
|
|
180
|
+
identity_document_scan: {
|
|
181
|
+
started: AnalyticsEventName.DOCUMENT_SCAN_STARTED,
|
|
182
|
+
completed: AnalyticsEventName.DOCUMENT_SCAN_COMPLETED
|
|
183
|
+
},
|
|
184
|
+
identity_document_eid_scan: {
|
|
185
|
+
started: AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_STARTED,
|
|
186
|
+
completed: AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_COMPLETED
|
|
187
|
+
},
|
|
188
|
+
liveness_check: {
|
|
189
|
+
started: AnalyticsEventName.LIVENESS_CHECK_STARTED,
|
|
190
|
+
completed: AnalyticsEventName.LIVENESS_CHECK_COMPLETED
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
function getStepEventName(stepType, suffix) {
|
|
194
|
+
const normalizedStep = stepType.toLowerCase();
|
|
195
|
+
const mapping = STEP_EVENT_MAP[normalizedStep];
|
|
196
|
+
if (mapping) {
|
|
197
|
+
return mapping[suffix];
|
|
198
|
+
}
|
|
199
|
+
// Fallback to dynamic name for unknown step types
|
|
200
|
+
return `${normalizedStep}_${suffix}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Track verification process events
|
|
205
|
+
*/
|
|
206
|
+
export const trackVerificationStart = async stepType => {
|
|
207
|
+
try {
|
|
208
|
+
const eventName = getStepEventName(stepType, 'started');
|
|
209
|
+
await analyticsService.trackEvent(eventName, AnalyticsEventCategory.VERIFICATION, {
|
|
210
|
+
stepType
|
|
211
|
+
});
|
|
212
|
+
} catch (error) {
|
|
213
|
+
// Silently fail - analytics should never block app flow
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
export const trackVerificationComplete = async (stepType, success, attemptNumber) => {
|
|
217
|
+
try {
|
|
218
|
+
const eventName = getStepEventName(stepType, 'completed');
|
|
219
|
+
await analyticsService.trackEvent(eventName, AnalyticsEventCategory.VERIFICATION, {
|
|
220
|
+
stepType,
|
|
221
|
+
success,
|
|
222
|
+
attemptNumber
|
|
223
|
+
});
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// Silently fail - analytics should never block app flow
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Track session events
|
|
231
|
+
*/
|
|
232
|
+
export const trackSessionStart = async sessionId => {
|
|
233
|
+
try {
|
|
234
|
+
await analyticsService.trackEvent(AnalyticsEventName.SESSION_START, AnalyticsEventCategory.SESSION, {
|
|
235
|
+
sessionId
|
|
236
|
+
});
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// Silently fail - analytics should never block app flow
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
export const trackSessionEnd = async (sessionId, reason) => {
|
|
242
|
+
try {
|
|
243
|
+
await analyticsService.trackEvent(AnalyticsEventName.SESSION_END, AnalyticsEventCategory.SESSION, {
|
|
244
|
+
sessionId,
|
|
245
|
+
reason
|
|
246
|
+
});
|
|
247
|
+
} catch (error) {
|
|
248
|
+
// Silently fail - analytics should never block app flow
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Track NFC scan events with detailed metadata
|
|
254
|
+
*/
|
|
255
|
+
export const trackNFCScanStart = async (documentType, nfcSupported, nfcEnabled, attemptNumber = 1) => {
|
|
256
|
+
try {
|
|
257
|
+
await analyticsService.trackEvent(AnalyticsEventName.NFC_SCAN_STARTED, AnalyticsEventCategory.VERIFICATION, {
|
|
258
|
+
verificationType: 'nfc',
|
|
259
|
+
documentType,
|
|
260
|
+
nfcSupported,
|
|
261
|
+
nfcEnabled,
|
|
262
|
+
attemptNumber
|
|
263
|
+
});
|
|
264
|
+
} catch (error) {
|
|
265
|
+
// Silently fail - analytics should never block app flow
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
export const trackNFCScanComplete = async (documentType, scanDuration, attemptNumber = 1) => {
|
|
269
|
+
try {
|
|
270
|
+
await analyticsService.trackEvent(AnalyticsEventName.NFC_SCAN_COMPLETED, AnalyticsEventCategory.VERIFICATION, {
|
|
271
|
+
verificationType: 'nfc',
|
|
272
|
+
success: true,
|
|
273
|
+
documentType,
|
|
274
|
+
scanDuration,
|
|
275
|
+
attemptNumber
|
|
276
|
+
});
|
|
277
|
+
} catch (error) {
|
|
278
|
+
// Silently fail - analytics should never block app flow
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
export const trackNFCScanFailed = async (documentType, errorType, errorMessage, scanDuration, attemptNumber = 1, deviceModel, osVersion) => {
|
|
282
|
+
try {
|
|
283
|
+
const eventName = errorType === 'device_unsupported' ? AnalyticsEventName.NFC_DEVICE_UNSUPPORTED : errorType === 'user_cancelled' ? AnalyticsEventName.NFC_USER_CANCELLED : errorType === 'reading_error' ? AnalyticsEventName.NFC_READING_ERROR : AnalyticsEventName.NFC_SCAN_FAILED;
|
|
284
|
+
await analyticsService.trackEvent(eventName, AnalyticsEventCategory.ERROR, {
|
|
285
|
+
verificationType: 'nfc',
|
|
286
|
+
success: false,
|
|
287
|
+
documentType,
|
|
288
|
+
errorType,
|
|
289
|
+
errorMessage,
|
|
290
|
+
scanDuration,
|
|
291
|
+
attemptNumber,
|
|
292
|
+
deviceModel,
|
|
293
|
+
osVersion
|
|
294
|
+
});
|
|
295
|
+
} catch (error) {
|
|
296
|
+
// Silently fail - analytics should never block app flow
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* EID (identity document with NFC) specific events
|
|
302
|
+
*/
|
|
303
|
+
export const trackEIDScanStart = async (documentType, nfcSupported, nfcEnabled, attemptNumber = 1) => {
|
|
304
|
+
try {
|
|
305
|
+
await analyticsService.trackEvent(AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_STARTED, AnalyticsEventCategory.VERIFICATION, {
|
|
306
|
+
verificationType: 'nfc',
|
|
307
|
+
documentType,
|
|
308
|
+
nfcSupported,
|
|
309
|
+
nfcEnabled,
|
|
310
|
+
attemptNumber
|
|
311
|
+
});
|
|
312
|
+
} catch (error) {
|
|
313
|
+
// Silently fail - analytics should never block app flow
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
export const trackEIDScanComplete = async (documentType, scanDuration, attemptNumber = 1) => {
|
|
317
|
+
try {
|
|
318
|
+
await analyticsService.trackEvent(AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_COMPLETED, AnalyticsEventCategory.VERIFICATION, {
|
|
319
|
+
verificationType: 'nfc',
|
|
320
|
+
success: true,
|
|
321
|
+
documentType,
|
|
322
|
+
scanDuration,
|
|
323
|
+
attemptNumber
|
|
324
|
+
});
|
|
325
|
+
} catch (error) {
|
|
326
|
+
// Silently fail - analytics should never block app flow
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
export const trackEIDScanFailed = async (documentType, errorType, errorMessage, scanDuration, attemptNumber = 1, deviceModel, osVersion) => {
|
|
330
|
+
try {
|
|
331
|
+
await analyticsService.trackEvent(AnalyticsEventName.IDENTITY_DOCUMENT_EID_SCAN_FAILED, AnalyticsEventCategory.ERROR, {
|
|
332
|
+
verificationType: 'nfc',
|
|
333
|
+
success: false,
|
|
334
|
+
documentType,
|
|
335
|
+
errorType,
|
|
336
|
+
errorMessage,
|
|
337
|
+
scanDuration,
|
|
338
|
+
attemptNumber,
|
|
339
|
+
deviceModel,
|
|
340
|
+
osVersion
|
|
341
|
+
});
|
|
342
|
+
} catch (error) {
|
|
343
|
+
// Silently fail - analytics should never block app flow
|
|
344
|
+
}
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Track step abandonment
|
|
349
|
+
*/
|
|
350
|
+
export const trackStepAbandoned = async (stepName, stepIndex, totalSteps, timeOnStep) => {
|
|
351
|
+
try {
|
|
352
|
+
await analyticsService.trackEvent(AnalyticsEventName.STEP_ABANDONED, AnalyticsEventCategory.USER_ACTION, {
|
|
353
|
+
stepName,
|
|
354
|
+
stepIndex,
|
|
355
|
+
totalSteps,
|
|
356
|
+
timeOnStep,
|
|
357
|
+
isRequired: true
|
|
358
|
+
});
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// Silently fail - analytics should never block app flow
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Track step skipped
|
|
366
|
+
*/
|
|
367
|
+
export const trackStepSkipped = async (stepName, stepIndex, totalSteps, previousStep) => {
|
|
368
|
+
try {
|
|
369
|
+
await analyticsService.trackEvent(AnalyticsEventName.STEP_SKIPPED, AnalyticsEventCategory.USER_ACTION, {
|
|
370
|
+
stepName,
|
|
371
|
+
stepIndex,
|
|
372
|
+
totalSteps,
|
|
373
|
+
previousStep,
|
|
374
|
+
isRequired: false
|
|
375
|
+
});
|
|
376
|
+
} catch (error) {
|
|
377
|
+
// Silently fail - analytics should never block app flow
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Track consent/contract acceptance
|
|
383
|
+
*/
|
|
384
|
+
export const trackConsentGiven = async (contractIds, screenName = 'contract_acceptance') => {
|
|
385
|
+
try {
|
|
386
|
+
await analyticsService.trackEvent(AnalyticsEventName.CONSENT_GIVEN, AnalyticsEventCategory.USER_ACTION, {
|
|
387
|
+
screenName,
|
|
388
|
+
contractIds: contractIds.join(','),
|
|
389
|
+
consentType: 'contract_acceptance'
|
|
390
|
+
});
|
|
391
|
+
} catch (error) {
|
|
392
|
+
// Silently fail - analytics should never block app flow
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Track funnel step progress
|
|
398
|
+
*/
|
|
399
|
+
export const trackFunnelStep = async (stepName, stepIndex, totalSteps, previousStep, isRequired = true) => {
|
|
400
|
+
try {
|
|
401
|
+
await analyticsService.trackEvent(AnalyticsEventName.SCREEN_VIEW, AnalyticsEventCategory.NAVIGATION, {
|
|
402
|
+
screenName: stepName,
|
|
403
|
+
stepName,
|
|
404
|
+
stepIndex,
|
|
405
|
+
totalSteps,
|
|
406
|
+
previousStep,
|
|
407
|
+
isRequired
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
// Silently fail - analytics should never block app flow
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* React hook for screen tracking
|
|
416
|
+
*/
|
|
417
|
+
export const useScreenTracking = screenName => {
|
|
418
|
+
const [screenStartTime] = useState(Date.now());
|
|
419
|
+
useEffect(() => {
|
|
420
|
+
// Track screen view on mount
|
|
421
|
+
trackScreenView(screenName);
|
|
422
|
+
|
|
423
|
+
// Track screen exit on unmount with current screenName captured in closure
|
|
424
|
+
const currentScreen = screenName;
|
|
425
|
+
return () => {
|
|
426
|
+
const duration = Date.now() - screenStartTime;
|
|
427
|
+
trackScreenExit(currentScreen, duration);
|
|
428
|
+
};
|
|
429
|
+
}, [screenName, screenStartTime]);
|
|
430
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Get average brightness for entire frame (center area)
|
|
5
|
+
*/
|
|
3
6
|
const getAverageBrightness = frame => {
|
|
4
7
|
'worklet';
|
|
5
8
|
|
|
@@ -24,9 +27,62 @@ const getAverageBrightness = frame => {
|
|
|
24
27
|
}
|
|
25
28
|
return luminanceSum / pixelCount;
|
|
26
29
|
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get average brightness for a specific circular region (for face detection)
|
|
33
|
+
* Calculates brightness only for pixels inside the circle defined by centerX, centerY, and radius
|
|
34
|
+
*/
|
|
35
|
+
const getCircularRegionBrightness = (frame, circleRect) => {
|
|
36
|
+
'worklet';
|
|
37
|
+
|
|
38
|
+
const buffer = frame.toArrayBuffer();
|
|
39
|
+
const data = new Uint8Array(buffer);
|
|
40
|
+
const width = frame.width;
|
|
41
|
+
const height = frame.height;
|
|
42
|
+
|
|
43
|
+
// Calculate circle parameters from rect
|
|
44
|
+
const centerX = Math.floor(circleRect.minX + circleRect.width / 2);
|
|
45
|
+
const centerY = Math.floor(circleRect.minY + circleRect.height / 2);
|
|
46
|
+
const radius = Math.floor(Math.min(circleRect.width, circleRect.height) / 2);
|
|
47
|
+
let luminanceSum = 0;
|
|
48
|
+
let pixelCount = 0;
|
|
49
|
+
|
|
50
|
+
// Calculate bounding box for the circle to optimize iteration
|
|
51
|
+
const minX = Math.max(0, centerX - radius);
|
|
52
|
+
const maxX = Math.min(width - 1, centerX + radius);
|
|
53
|
+
const minY = Math.max(0, centerY - radius);
|
|
54
|
+
const maxY = Math.min(height - 1, centerY + radius);
|
|
55
|
+
|
|
56
|
+
// Iterate only over pixels in the bounding box
|
|
57
|
+
for (let y = minY; y <= maxY; y++) {
|
|
58
|
+
for (let x = minX; x <= maxX; x++) {
|
|
59
|
+
// Check if pixel is inside the circle
|
|
60
|
+
const dx = x - centerX;
|
|
61
|
+
const dy = y - centerY;
|
|
62
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
63
|
+
if (distanceSquared <= radius * radius) {
|
|
64
|
+
const index = y * width + x;
|
|
65
|
+
if (data[index] !== undefined) {
|
|
66
|
+
luminanceSum += data[index];
|
|
67
|
+
pixelCount++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return pixelCount > 0 ? luminanceSum / pixelCount : 0;
|
|
73
|
+
};
|
|
27
74
|
const isFrameBright = frame => {
|
|
28
75
|
'worklet';
|
|
29
76
|
|
|
30
|
-
return getAverageBrightness(frame) >
|
|
77
|
+
return getAverageBrightness(frame) > 60;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a circular region in the frame is bright enough
|
|
82
|
+
*/
|
|
83
|
+
const isCircularRegionBright = (frame, circleRect, threshold = 60) => {
|
|
84
|
+
'worklet';
|
|
85
|
+
|
|
86
|
+
return getCircularRegionBrightness(frame, circleRect) > threshold;
|
|
31
87
|
};
|
|
32
|
-
export { isFrameBright, getAverageBrightness };
|
|
88
|
+
export { isFrameBright, getAverageBrightness, getCircularRegionBrightness, isCircularRegionBright };
|
|
@@ -3,18 +3,26 @@
|
|
|
3
3
|
const handleDeepLink = ({
|
|
4
4
|
url
|
|
5
5
|
}) => {
|
|
6
|
+
console.log('[handleDeepLink] Received URL:', url);
|
|
6
7
|
const isHttps = url.includes('https');
|
|
7
8
|
const route = url.replace(/http(s)?(:)?(\/\/)?/g, '');
|
|
8
9
|
const segments = route.split('/');
|
|
10
|
+
console.log('[handleDeepLink] Parsed segments:', segments);
|
|
9
11
|
let baseUrl = '';
|
|
10
12
|
let sessionId = '';
|
|
11
13
|
for (let i = 0; i < segments.length; i++) {
|
|
12
14
|
if (segments[i] === 'verification-session') {
|
|
13
15
|
sessionId = segments[i + 1] ?? '';
|
|
16
|
+
console.log('[handleDeepLink] Found sessionId:', sessionId);
|
|
14
17
|
} else if (segments[i] === 'app-url') {
|
|
15
18
|
baseUrl = `${isHttps ? 'https' : 'http'}://${segments[i + 1]}`;
|
|
19
|
+
console.log('[handleDeepLink] Found baseUrl:', baseUrl);
|
|
16
20
|
}
|
|
17
21
|
}
|
|
22
|
+
console.log('[handleDeepLink] Returning:', {
|
|
23
|
+
baseUrl,
|
|
24
|
+
sessionId
|
|
25
|
+
});
|
|
18
26
|
return [baseUrl, sessionId];
|
|
19
27
|
};
|
|
20
28
|
export { handleDeepLink };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
+
import { trackApiCall, trackError } from "./analytics.utils.js";
|
|
3
4
|
export class HttpClientError extends Error {
|
|
4
5
|
constructor(message) {
|
|
5
6
|
super(message);
|
|
@@ -38,39 +39,99 @@ const request = async (httpMethod, url, body, simulatedResponse) => {
|
|
|
38
39
|
}, 1000);
|
|
39
40
|
});
|
|
40
41
|
}
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'Content-Type': 'application/json'
|
|
45
|
-
},
|
|
46
|
-
body: body ? JSON.stringify(body) : undefined
|
|
47
|
-
});
|
|
48
|
-
let responseJson = null;
|
|
42
|
+
const startTime = Date.now();
|
|
43
|
+
let statusCode = 0;
|
|
44
|
+
let success = false;
|
|
49
45
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
46
|
+
const response = await fetch(url, {
|
|
47
|
+
method: httpMethod,
|
|
48
|
+
headers: {
|
|
49
|
+
'Content-Type': 'application/json'
|
|
50
|
+
},
|
|
51
|
+
body: body ? JSON.stringify(body) : undefined
|
|
52
|
+
});
|
|
53
|
+
statusCode = response.status;
|
|
54
|
+
success = response.ok;
|
|
55
|
+
let responseJson = null;
|
|
56
|
+
try {
|
|
57
|
+
responseJson = await response.json();
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Invalid JSON response
|
|
64
60
|
}
|
|
65
|
-
|
|
66
|
-
|
|
61
|
+
|
|
62
|
+
// Track API call performance (non-blocking)
|
|
63
|
+
// Wrap in try-catch to ensure analytics failures never mask HTTP errors
|
|
64
|
+
try {
|
|
65
|
+
const duration = Date.now() - startTime;
|
|
66
|
+
trackApiCall(url, duration, statusCode, success).catch(() => {});
|
|
67
|
+
} catch {}
|
|
68
|
+
if (response && !response.ok && responseJson) {
|
|
69
|
+
const message = responseJson.message;
|
|
70
|
+
const getErrorCodeFromStatus = status => {
|
|
71
|
+
switch (status) {
|
|
72
|
+
case 400:
|
|
73
|
+
return 'BAD_REQUEST';
|
|
74
|
+
case 401:
|
|
75
|
+
return 'UNAUTHORIZED';
|
|
76
|
+
case 403:
|
|
77
|
+
return 'FORBIDDEN';
|
|
78
|
+
case 404:
|
|
79
|
+
return 'NOT_FOUND';
|
|
80
|
+
case 500:
|
|
81
|
+
return 'INTERNAL_SERVER_ERROR';
|
|
82
|
+
case 502:
|
|
83
|
+
return 'BAD_GATEWAY';
|
|
84
|
+
case 503:
|
|
85
|
+
return 'SERVICE_UNAVAILABLE';
|
|
86
|
+
case 504:
|
|
87
|
+
return 'GATEWAY_TIMEOUT';
|
|
88
|
+
default:
|
|
89
|
+
return `API_ERROR_${status}`;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const getSeverityFromStatus = status => {
|
|
93
|
+
if (status >= 500) return 'high';
|
|
94
|
+
if (status === 401 || status === 403) return 'medium';
|
|
95
|
+
if (status === 400 || status === 404) return 'low';
|
|
96
|
+
return 'medium';
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Track API error (non-blocking)
|
|
100
|
+
// Wrap in try-catch to ensure analytics failures never mask HTTP errors
|
|
101
|
+
try {
|
|
102
|
+
trackError(getErrorCodeFromStatus(statusCode), message || 'API request failed', 'http_client', getSeverityFromStatus(statusCode)).catch(() => {});
|
|
103
|
+
} catch {}
|
|
104
|
+
if (response.status === 401) {
|
|
105
|
+
throw new UnauthorizedError(message);
|
|
106
|
+
}
|
|
107
|
+
if (response.status === 403) {
|
|
108
|
+
throw new ForbiddenError(message);
|
|
109
|
+
}
|
|
110
|
+
if (response.status === 404) {
|
|
111
|
+
throw new NotFoundError(message);
|
|
112
|
+
}
|
|
113
|
+
if (response.status >= 500) {
|
|
114
|
+
throw new InternalServerError(message);
|
|
115
|
+
}
|
|
116
|
+
if (response.status >= 400) {
|
|
117
|
+
throw new BadRequestError(message);
|
|
118
|
+
}
|
|
119
|
+
throw new Error(message);
|
|
67
120
|
}
|
|
68
|
-
|
|
69
|
-
|
|
121
|
+
return responseJson;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// Only track network errors for actual network failures, not HTTP errors
|
|
124
|
+
// HTTP errors (4xx, 5xx) are already tracked above before throwing
|
|
125
|
+
if (!(error instanceof HttpClientError)) {
|
|
126
|
+
try {
|
|
127
|
+
const duration = Date.now() - startTime;
|
|
128
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
129
|
+
trackApiCall(url, duration, statusCode, false).catch(() => {});
|
|
130
|
+
trackError('NETWORK_ERROR', errorMessage, 'http_client', 'high').catch(() => {});
|
|
131
|
+
} catch {}
|
|
70
132
|
}
|
|
71
|
-
throw
|
|
133
|
+
throw error;
|
|
72
134
|
}
|
|
73
|
-
return responseJson;
|
|
74
135
|
};
|
|
75
136
|
const get = (url, simulatedResponse) => {
|
|
76
137
|
return request('GET', url, undefined, simulatedResponse);
|