@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.
Files changed (88) 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/module/version.js +5 -0
  30. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  31. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  32. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  33. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  35. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  36. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  37. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  38. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
  39. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  42. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  43. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  44. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
  45. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
  46. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
  47. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  48. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  50. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
  51. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
  52. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
  53. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
  54. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
  55. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  56. package/lib/typescript/src/Translation/index.d.ts.map +1 -1
  57. package/lib/typescript/src/Trustchex.d.ts +1 -0
  58. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  59. package/lib/typescript/src/index.d.ts +4 -0
  60. package/lib/typescript/src/index.d.ts.map +1 -1
  61. package/lib/typescript/src/version.d.ts +2 -0
  62. package/lib/typescript/src/version.d.ts.map +1 -0
  63. package/package.json +6 -2
  64. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
  65. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
  66. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
  67. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
  68. package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
  69. package/src/Screens/Static/ResultScreen.tsx +79 -4
  70. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +65 -10
  71. package/src/Shared/Components/EIDScanner.tsx +132 -3
  72. package/src/Shared/Components/FaceCamera.tsx +77 -2
  73. package/src/Shared/Components/IdentityDocumentCamera.tsx +4 -4
  74. package/src/Shared/Components/NavigationManager.tsx +2 -0
  75. package/src/Shared/Components/QrCodeScannerCamera.tsx +2 -0
  76. package/src/Shared/Contexts/AppContext.ts +4 -0
  77. package/src/Shared/Libs/analytics.utils.ts +644 -0
  78. package/src/Shared/Libs/camera.utils.ts +74 -2
  79. package/src/Shared/Libs/deeplink.utils.ts +5 -0
  80. package/src/Shared/Libs/http-client.ts +105 -31
  81. package/src/Shared/Services/AnalyticsService.ts +470 -0
  82. package/src/Shared/Types/analytics.types.ts +179 -0
  83. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
  84. package/src/Translation/Resources/tr.ts +2 -1
  85. package/src/Translation/index.ts +9 -0
  86. package/src/Trustchex.tsx +54 -2
  87. package/src/index.tsx +33 -0
  88. package/src/version.ts +3 -0
@@ -10,6 +10,12 @@ import IdentityDocumentCamera, {
10
10
  type DocumentScannedData,
11
11
  } from '../../Shared/Components/IdentityDocumentCamera';
12
12
  import { useTranslation } from 'react-i18next';
13
+ import {
14
+ trackFunnelStep,
15
+ useScreenTracking,
16
+ trackVerificationStart,
17
+ trackVerificationComplete,
18
+ } from '../../Shared/Libs/analytics.utils';
13
19
 
14
20
  const IdentityDocumentEIDScanningScreen = () => {
15
21
  const appContext = useContext(AppContext);
@@ -33,6 +39,14 @@ const IdentityDocumentEIDScanningScreen = () => {
33
39
  null
34
40
  );
35
41
 
42
+ // Track screen view and exit
43
+ useScreenTracking('nfc_scanning');
44
+
45
+ // Track EID scan step started when component mounts
46
+ useEffect(() => {
47
+ trackVerificationStart('IDENTITY_DOCUMENT_EID_SCAN');
48
+ }, []);
49
+
36
50
  useEffect(() => {
37
51
  if (appContext.identificationInfo.scannedDocument?.mrzFields) {
38
52
  const {
@@ -105,6 +119,8 @@ const IdentityDocumentEIDScanningScreen = () => {
105
119
  if (allowedCountries && allowedCountries.length > 0) {
106
120
  const countryCode = mrzFields.issuingState;
107
121
  if (!allowedCountries.includes(countryCode as 'TUR')) {
122
+ // Workflow validation - user's document country not in allowed list
123
+ // Expected behavior, not a bug
108
124
  Alert.alert(
109
125
  t('general.error'),
110
126
  t('general.countryNotAllowed')
@@ -120,6 +136,8 @@ const IdentityDocumentEIDScanningScreen = () => {
120
136
  if (
121
137
  !allowedDocumentTypes.includes(documentCode as 'I' | 'P')
122
138
  ) {
139
+ // Workflow validation - user's document type not in allowed list
140
+ // Expected behavior, not a bug
123
141
  Alert.alert(
124
142
  t('general.error'),
125
143
  t('general.documentTypeNotAllowed')
@@ -130,6 +148,18 @@ const IdentityDocumentEIDScanningScreen = () => {
130
148
  }
131
149
  }
132
150
 
151
+ // Track successful EID step completion
152
+ trackVerificationComplete('IDENTITY_DOCUMENT_EID_SCAN', true, 1);
153
+
154
+ // Track successful NFC completion as funnel step
155
+ trackFunnelStep(
156
+ 'NFC Scan Completed',
157
+ 4,
158
+ 5,
159
+ 'liveness_detection',
160
+ true
161
+ );
162
+
133
163
  navigationManagerRef.current?.navigateToNextStep();
134
164
  }
135
165
  }}
@@ -10,6 +10,12 @@ import NavigationManager, {
10
10
  type NavigationManagerRef,
11
11
  } from '../../Shared/Components/NavigationManager';
12
12
  import { useTranslation } from 'react-i18next';
13
+ import {
14
+ trackFunnelStep,
15
+ useScreenTracking,
16
+ trackVerificationStart,
17
+ trackVerificationComplete,
18
+ } from '../../Shared/Libs/analytics.utils';
13
19
 
14
20
  const IdentityDocumentScanningScreen = () => {
15
21
  const [idFrontSideData, setIDFrontSideData] =
@@ -28,6 +34,14 @@ const IdentityDocumentScanningScreen = () => {
28
34
  null
29
35
  );
30
36
 
37
+ // Track screen view and exit
38
+ useScreenTracking('document_scanning');
39
+
40
+ // Track document scan started when component mounts
41
+ useEffect(() => {
42
+ trackVerificationStart('IDENTITY_DOCUMENT_SCAN');
43
+ }, []);
44
+
31
45
  useEffect(() => {
32
46
  if (
33
47
  ((idFrontSideData && idBackSideData) || passportData) &&
@@ -71,6 +85,8 @@ const IdentityDocumentScanningScreen = () => {
71
85
  if (allowedCountries && allowedCountries.length > 0) {
72
86
  const countryCode = mrzFields.issuingState;
73
87
  if (!allowedCountries.includes(countryCode as 'TUR')) {
88
+ // Workflow validation - user's document country not in allowed list
89
+ // Expected behavior, not a bug
74
90
  Alert.alert(t('general.error'), t('general.countryNotAllowed'));
75
91
  appContext.onError?.(t('general.countryNotAllowed'));
76
92
  navigationManagerRef.current?.reset();
@@ -81,6 +97,8 @@ const IdentityDocumentScanningScreen = () => {
81
97
  if (allowedDocumentTypes && allowedDocumentTypes.length > 0) {
82
98
  const documentCode = mrzFields.documentCode;
83
99
  if (!allowedDocumentTypes.includes(documentCode as 'I' | 'P')) {
100
+ // Workflow validation - user's document type not in allowed list
101
+ // Expected behavior, not a bug
84
102
  Alert.alert(t('general.error'), t('general.documentTypeNotAllowed'));
85
103
  appContext.onError?.(t('general.documentTypeNotAllowed'));
86
104
  navigationManagerRef.current?.reset();
@@ -88,6 +106,18 @@ const IdentityDocumentScanningScreen = () => {
88
106
  }
89
107
  }
90
108
 
109
+ // Track successful document scan completion
110
+ trackVerificationComplete('IDENTITY_DOCUMENT_SCAN', true, 1);
111
+
112
+ // Track successful document scan completion as funnel step
113
+ trackFunnelStep(
114
+ 'Document Scan Completed',
115
+ 2,
116
+ 5,
117
+ 'contract_acceptance',
118
+ true
119
+ );
120
+
91
121
  setTimeout(
92
122
  () => navigationManagerRef.current?.navigateToNextStep(),
93
123
  1000
@@ -28,6 +28,12 @@ import { useTranslation } from 'react-i18next';
28
28
  import StyledButton from '../../Shared/Components/StyledButton';
29
29
  import LottieView from 'lottie-react-native';
30
30
  import { speakWithDebounce } from '../../Shared/Libs/tts.utils';
31
+ import {
32
+ trackFunnelStep,
33
+ useScreenTracking,
34
+ trackVerificationStart,
35
+ trackVerificationComplete,
36
+ } from '../../Shared/Libs/analytics.utils';
31
37
 
32
38
  const { width: windowWidth, height: windowHeight } = Dimensions.get('window');
33
39
 
@@ -80,6 +86,9 @@ const LivenessDetectionScreen = () => {
80
86
  const navigationManagerRef = React.useRef<NavigationManagerRef>(null);
81
87
  const { t } = useTranslation();
82
88
  const [isRecording, setIsRecording] = useState(false);
89
+
90
+ // Track screen view and exit
91
+ useScreenTracking('liveness_detection');
83
92
  const [initialState, setInitialState] = useState<StateType>({
84
93
  brightnessLow: false,
85
94
  faceDetected: false,
@@ -170,20 +179,35 @@ const LivenessDetectionScreen = () => {
170
179
  try {
171
180
  await camera?.cancelRecording();
172
181
  setIsRecording(false);
173
- } catch (e) {
174
- // Ignore error
182
+ } catch (error) {
183
+ // User cancelled recording - expected behavior, no need to track
175
184
  }
176
185
  }
177
186
  setTimeout(() => {
187
+ // Track liveness check started
188
+ trackVerificationStart('LIVENESS_CHECK');
189
+
178
190
  camera?.startRecording({
179
191
  fileType: 'mp4',
180
192
  videoCodec: 'h265',
181
193
  onRecordingError() {
194
+ // Recording errors are retried automatically, no need to track them
182
195
  setIsRecording(false);
183
196
  },
184
197
  onRecordingFinished(video) {
185
198
  setTimeout(() => {
186
199
  dispatch({ type: 'VIDEO_RECORDED', payload: video.path });
200
+
201
+ // Track liveness check completion
202
+ trackVerificationComplete('LIVENESS_CHECK', true, 1);
203
+
204
+ trackFunnelStep(
205
+ 'Liveness Check Completed',
206
+ 3,
207
+ 5,
208
+ 'document_scanning',
209
+ true
210
+ );
187
211
  setIsRecording(false);
188
212
  }, 500);
189
213
  },
@@ -196,8 +220,9 @@ const LivenessDetectionScreen = () => {
196
220
  try {
197
221
  await camera?.stopRecording();
198
222
  setIsRecording(false);
199
- } catch (e) {
200
- // Ignore error
223
+ } catch (error) {
224
+ // Stop recording can fail due to race conditions when user navigates away
225
+ // This is expected behavior and not actionable
201
226
  }
202
227
  }, [camera]);
203
228
 
@@ -528,6 +553,7 @@ const LivenessDetectionScreen = () => {
528
553
  <FaceCamera
529
554
  onFacesDetected={onFacesDetected}
530
555
  onCameraInitialized={setCamera}
556
+ previewRect={PREVIEW_RECT}
531
557
  />
532
558
  <NativeCircularProgress
533
559
  style={styles.circularProgress}
@@ -14,8 +14,18 @@ const QrCodeScanningScreen = () => {
14
14
  const [bUrl, sId] = handleDeepLink({ url: data });
15
15
 
16
16
  if (bUrl && sId) {
17
- appContext.baseUrl = bUrl;
18
- appContext.identificationInfo.sessionId = sId;
17
+ if (appContext.setBaseUrl) {
18
+ appContext.setBaseUrl(bUrl);
19
+ } else {
20
+ appContext.baseUrl = bUrl;
21
+ }
22
+
23
+ if (appContext.setSessionId) {
24
+ appContext.setSessionId(sId);
25
+ } else {
26
+ appContext.identificationInfo.sessionId = sId;
27
+ }
28
+
19
29
  navigation.navigate('VerificationSessionCheckScreen' as never);
20
30
  }
21
31
  };
@@ -36,6 +36,16 @@ import { encryptWithAes, getSessionKey } from '../../Shared/Libs/crypto.utils';
36
36
  import Video from 'react-native-video';
37
37
  import StyledButton from '../../Shared/Components/StyledButton';
38
38
  import NativeDeviceInfo from '../../Shared/Libs/native-device-info.utils';
39
+ import {
40
+ trackError,
41
+ trackFunnelStep,
42
+ useScreenTracking,
43
+ } from '../../Shared/Libs/analytics.utils';
44
+ import { analyticsService } from '../../Shared/Services/AnalyticsService';
45
+ import {
46
+ AnalyticsEventName,
47
+ AnalyticsEventCategory,
48
+ } from '../../Shared/Types/analytics.types';
39
49
 
40
50
  const ResultScreen = () => {
41
51
  const appContext = useContext(AppContext);
@@ -46,6 +56,9 @@ const ResultScreen = () => {
46
56
  const [deviceIdentifier, setDeviceIdentifier] = useState<string>('');
47
57
  const { t } = useTranslation();
48
58
 
59
+ // Track screen view and exit
60
+ useScreenTracking('result_screen');
61
+
49
62
  const apiUrl = useMemo(
50
63
  () => `${appContext.baseUrl}/api/app/mobile`,
51
64
  [appContext.baseUrl]
@@ -197,7 +210,7 @@ const ResultScreen = () => {
197
210
  files: [],
198
211
  progress: (res) => {
199
212
  setProgress(
200
- 40 + (res.totalBytesSent / res.totalBytesExpectedToSend) * 50
213
+ 60 + (res.totalBytesSent / res.totalBytesExpectedToSend) * 30
201
214
  );
202
215
  },
203
216
  } satisfies RNFS.UploadFileOptions;
@@ -335,7 +348,7 @@ const ResultScreen = () => {
335
348
 
336
349
  const livenessVideoPath = livenessDetection.videoPath;
337
350
  if (livenessVideoPath) {
338
- let videoFilePath;
351
+ let videoFilePath: string;
339
352
  if (Platform.OS === 'ios') {
340
353
  await RNFS.mkdir(
341
354
  `${RNFS.TemporaryDirectoryPath}/${new Date().getTime()}`
@@ -345,11 +358,44 @@ const ResultScreen = () => {
345
358
  videoFilePath = `${RNFS.TemporaryDirectoryPath}/LIVENESS_VIDEO.mp4`;
346
359
  }
347
360
  await RNFS.copyFile(livenessVideoPath, videoFilePath);
348
- await VideoCompressor.compress(videoFilePath);
361
+
362
+ // Get original file size
363
+ const originalStats = await RNFS.stat(videoFilePath);
364
+ const originalSize = originalStats.size;
365
+
366
+ // Compress video with maximum compression settings
367
+ const compressedVideoPath = await VideoCompressor.compress(
368
+ videoFilePath,
369
+ {
370
+ compressionMethod: 'manual',
371
+ bitrate: 500000, // 500 kbps
372
+ maxSize: 1280, // HD 720p
373
+ minimumFileSizeForCompress: 0, // Always compress
374
+ },
375
+ (compressionProgress) => {
376
+ // Map compression progress (0-1) to main progress (40-60%)
377
+ setProgress(40 + compressionProgress * 20);
378
+ }
379
+ );
380
+
381
+ // Convert file:// URI to regular path for RNFS upload
382
+ const normalizedPath = compressedVideoPath.replace('file://', '');
383
+
384
+ // Get compressed file size and log difference
385
+ const compressedStats = await RNFS.stat(normalizedPath);
386
+ const compressedSize = compressedStats.size;
387
+ const compressionRatio = (
388
+ ((originalSize - compressedSize) / originalSize) *
389
+ 100
390
+ ).toFixed(2);
391
+ console.log(
392
+ `Video compression: ${(originalSize / 1024 / 1024).toFixed(2)}MB -> ${(compressedSize / 1024 / 1024).toFixed(2)}MB (${compressionRatio}% reduction)`
393
+ );
394
+
349
395
  uploadFileOptions.files.push({
350
396
  name: 'files',
351
397
  filename: 'LIVENESS_VIDEO.mp4',
352
- filepath: videoFilePath,
398
+ filepath: normalizedPath,
353
399
  filetype: 'video/mp4',
354
400
  });
355
401
  }
@@ -408,15 +454,44 @@ const ResultScreen = () => {
408
454
  livenessDetection
409
455
  )
410
456
  );
457
+ setProgress(90);
411
458
 
412
459
  await runWithRetry(() =>
413
460
  finishIdentification(identificationId, sessionKey)
414
461
  );
415
462
  setProgress(100);
416
463
  setIsSubmitting(false);
464
+
465
+ // Track successful verification completion as final funnel step
466
+ trackFunnelStep('Verification Completed', 5, 5, 'nfc_scanning', true).catch(() => {});
467
+ analyticsService.trackEvent(
468
+ AnalyticsEventName.VERIFICATION_SUCCESS,
469
+ AnalyticsEventCategory.VERIFICATION,
470
+ { sessionId: appContext.identificationInfo.sessionId || '' }
471
+ ).catch(() => {});
417
472
  } catch (error) {
418
473
  setIsSubmitting(false);
419
474
  setProgress(0);
475
+
476
+ // Track verification failure
477
+ const errorMessage =
478
+ error instanceof Error ? error.message : 'Unknown error';
479
+ trackError(
480
+ 'VERIFICATION_SUBMISSION_FAILED',
481
+ errorMessage,
482
+ 'result_screen',
483
+ 'critical',
484
+ { recoverable: false, userAction: 'submit_verification' }
485
+ ).catch(() => {});
486
+ analyticsService.trackEvent(
487
+ AnalyticsEventName.VERIFICATION_FAILED,
488
+ AnalyticsEventCategory.VERIFICATION,
489
+ {
490
+ sessionId: appContext.identificationInfo.sessionId || '',
491
+ errorMessage,
492
+ }
493
+ ).catch(() => {});
494
+
420
495
  Alert.alert(t('general.error'), t('resultScreen.submissionFailed'));
421
496
  appContext.onError?.(t('resultScreen.submissionFailed'));
422
497
  navigationManagerRef.current?.reset();
@@ -20,7 +20,7 @@ import {
20
20
  } from 'react-native';
21
21
  import { useSafeAreaInsets } from 'react-native-safe-area-context';
22
22
  import AppContext from '../../Shared/Contexts/AppContext';
23
- import httpClient, { NotFoundError } from '../../Shared/Libs/http-client';
23
+ import httpClient, { BadRequestError, NotFoundError } from '../../Shared/Libs/http-client';
24
24
  import { useTranslation } from 'react-i18next';
25
25
  import LanguageSelector from '../../Shared/Components/LanguageSelector';
26
26
  import NavigationManager, {
@@ -29,6 +29,7 @@ import NavigationManager, {
29
29
  import type { VerificationSession } from '../../Shared/Types/verificationSession';
30
30
  import StyledButton from '../../Shared/Components/StyledButton';
31
31
  import StyledTextInput from '../../Shared/Components/StyledTextInput';
32
+ import { analyticsService } from '../../Shared/Services/AnalyticsService';
32
33
  import { useTheme } from '../../Shared/Contexts/ThemeContext';
33
34
  import LottieView from 'lottie-react-native';
34
35
  import {
@@ -40,6 +41,11 @@ import {
40
41
  SESSION_CODE_CHARSET_PATTERN,
41
42
  SESSION_CODE_VALIDATION_PATTERN,
42
43
  } from '../../Shared/Constants/validation.constants';
44
+ import {
45
+ trackError,
46
+ trackFunnelStep,
47
+ useScreenTracking,
48
+ } from '../../Shared/Libs/analytics.utils';
43
49
 
44
50
  const VerificationSessionCheckScreen = () => {
45
51
  const [sessionCode, setSessionCode] = useState('');
@@ -57,6 +63,9 @@ const VerificationSessionCheckScreen = () => {
57
63
  const theme = useTheme();
58
64
  const primaryColor = theme.colors.primary;
59
65
 
66
+ // Track screen view and exit
67
+ useScreenTracking('verification_session_check');
68
+
60
69
  const apiUrl = useMemo(
61
70
  () => `${appContext.baseUrl}/api/app/mobile`,
62
71
  [appContext.baseUrl]
@@ -73,17 +82,26 @@ const VerificationSessionCheckScreen = () => {
73
82
  `${apiUrl}/verification-sessions/${id}`,
74
83
  appContext.isDemoSession
75
84
  ? getSimulatedDemoData<VerificationSession | undefined, void>(
76
- 'GET_SESSION'
77
- )
85
+ 'GET_SESSION'
86
+ )
78
87
  : undefined
79
88
  );
80
89
  return response;
81
90
  } catch (error) {
82
91
  if (error instanceof NotFoundError) {
92
+ // User entered invalid session ID - expected user behavior, not actionable
83
93
  Alert.alert(
84
94
  t('general.error'),
85
95
  t('verificationSessionCheckScreen.noVerificationSessionFound')
86
96
  );
97
+ } else {
98
+ trackError(
99
+ 'SESSION_CHECK_ERROR',
100
+ error instanceof Error ? error.message : 'Unknown error',
101
+ 'verification_session_check',
102
+ 'high',
103
+ { recoverable: false, userAction: 'check_session' }
104
+ );
87
105
  }
88
106
  }
89
107
  },
@@ -94,12 +112,13 @@ const VerificationSessionCheckScreen = () => {
94
112
  async (inputCode: string) => {
95
113
  try {
96
114
  appContext.isDemoSession = isDemoSession(inputCode);
115
+ analyticsService.setDemoSession(appContext.isDemoSession);
97
116
  const response = await httpClient.get<{ id: string }>(
98
117
  `${apiUrl}/verification-sessions?sessionCode=${inputCode.toUpperCase()}`,
99
118
  appContext.isDemoSession
100
119
  ? getSimulatedDemoData<{ id: string } | undefined, void>(
101
- 'GET_SESSION_BY_CODE'
102
- )
120
+ 'GET_SESSION_BY_CODE'
121
+ )
103
122
  : undefined
104
123
  );
105
124
 
@@ -107,13 +126,25 @@ const VerificationSessionCheckScreen = () => {
107
126
  throw new NotFoundError();
108
127
  }
109
128
 
129
+ // Track successful session start as funnel step
130
+ trackFunnelStep('Session Started', 1, 5, undefined, true);
131
+
110
132
  return response;
111
133
  } catch (error) {
112
134
  if (error instanceof NotFoundError) {
135
+ // User entered invalid session code - expected user behavior, not actionable
113
136
  Alert.alert(
114
137
  t('general.error'),
115
138
  t('verificationSessionCheckScreen.noVerificationSessionFound')
116
139
  );
140
+ } else {
141
+ trackError(
142
+ 'SESSION_CODE_CHECK_ERROR',
143
+ error instanceof Error ? error.message : 'Unknown error',
144
+ 'verification_session_check',
145
+ 'high',
146
+ { recoverable: false, userAction: 'check_session_code' }
147
+ );
117
148
  }
118
149
  }
119
150
  },
@@ -133,11 +164,19 @@ const VerificationSessionCheckScreen = () => {
133
164
  return true;
134
165
  } catch (error) {
135
166
  if (error instanceof NotFoundError) {
167
+ // Session expired or invalid - expected behavior, not actionable
136
168
  Alert.alert(
137
169
  t('general.error'),
138
170
  t('verificationSessionCheckScreen.noVerificationSessionFound')
139
171
  );
140
172
  } else {
173
+ trackError(
174
+ 'VERIFICATION_CODE_SEND_FAILED',
175
+ 'Failed to send verification code',
176
+ 'verification_session_check',
177
+ 'high',
178
+ { recoverable: true, userAction: 'send_verification_code' }
179
+ );
141
180
  Alert.alert(
142
181
  t('general.error'),
143
182
  t('verificationSessionCheckScreen.cannotSendVerificationCode')
@@ -162,19 +201,30 @@ const VerificationSessionCheckScreen = () => {
162
201
  },
163
202
  appContext.isDemoSession
164
203
  ? getSimulatedDemoData<
165
- VerificationSession | undefined,
166
- { code: string }
167
- >('GET_VERIFIED_SESSION', { code: verificationCode })
204
+ VerificationSession | undefined,
205
+ { code: string }
206
+ >('GET_VERIFIED_SESSION', { code: verificationCode })
168
207
  : undefined
169
208
  );
170
209
 
171
210
  return response;
172
211
  } catch (error) {
173
212
  if (error instanceof NotFoundError) {
213
+ // Session expired or invalid - expected behavior, not actionable
174
214
  Alert.alert(
175
215
  t('general.error'),
176
216
  t('verificationSessionCheckScreen.noVerificationSessionFound')
177
217
  );
218
+ } else if (error instanceof BadRequestError) {
219
+ // Wrong OTP code - expected user behavior, not actionable
220
+ } else {
221
+ trackError(
222
+ 'VERIFIED_SESSION_CHECK_ERROR',
223
+ error instanceof Error ? error.message : 'Unknown error',
224
+ 'verification_session_check',
225
+ 'high',
226
+ { recoverable: false, userAction: 'check_verified_session' }
227
+ );
178
228
  }
179
229
  }
180
230
  },
@@ -307,8 +357,12 @@ const VerificationSessionCheckScreen = () => {
307
357
  const session =
308
358
  await getSessionByCode(alphanumericText);
309
359
  if (session?.id) {
310
- appContext.identificationInfo.sessionId =
311
- session.id;
360
+ if (appContext.setSessionId) {
361
+ appContext.setSessionId(session.id);
362
+ } else {
363
+ appContext.identificationInfo.sessionId =
364
+ session.id;
365
+ }
312
366
  } else {
313
367
  setSessionCode('');
314
368
  }
@@ -383,6 +437,7 @@ const VerificationSessionCheckScreen = () => {
383
437
  setIsCodeSent(false);
384
438
  navigationManagerRef.current?.navigateToNextStep();
385
439
  } else {
440
+ // User entered wrong OTP code - expected behavior, not actionable
386
441
  appContext.onError?.('Invalid OTP code');
387
442
  Alert.alert(
388
443
  t('general.error'),