@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,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) > 80;
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 response = await fetch(url, {
42
- method: httpMethod,
43
- headers: {
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
- responseJson = await response.json();
51
- } catch (error) {
52
- // throw new Error("Invalid response");
53
- }
54
- if (response && !response.ok && responseJson) {
55
- const message = responseJson.message;
56
- if (response.status === 401) {
57
- throw new UnauthorizedError(message);
58
- }
59
- if (response.status === 403) {
60
- throw new ForbiddenError(message);
61
- }
62
- if (response.status === 404) {
63
- throw new NotFoundError(message);
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
- if (response.status >= 500) {
66
- throw new InternalServerError(message);
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
- if (response.status >= 400) {
69
- throw new BadRequestError(message);
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 new Error(message);
133
+ throw error;
72
134
  }
73
- return responseJson;
74
135
  };
75
136
  const get = (url, simulatedResponse) => {
76
137
  return request('GET', url, undefined, simulatedResponse);