@trustchex/react-native-sdk 1.250.0 → 1.266.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +43 -2
  2. package/android/src/main/java/com/trustchex/reactnativesdk/DeviceBrightnessModule.kt +66 -0
  3. package/android/src/main/java/com/trustchex/reactnativesdk/TrustchexSDKPackage.kt +12 -0
  4. package/ios/DeviceBrightnessModule.h +4 -0
  5. package/ios/DeviceBrightnessModule.m +27 -0
  6. package/lib/module/Screens/Dynamic/ContractAcceptanceScreen.js +25 -0
  7. package/lib/module/Screens/Dynamic/IdentityDocumentEIDScanningScreen.js +19 -0
  8. package/lib/module/Screens/Dynamic/IdentityDocumentScanningScreen.js +19 -0
  9. package/lib/module/Screens/Dynamic/LivenessDetectionScreen.js +18 -5
  10. package/lib/module/Screens/Static/QrCodeScanningScreen.js +10 -2
  11. package/lib/module/Screens/Static/ResultScreen.js +52 -3
  12. package/lib/module/Screens/Static/VerificationSessionCheckScreen.js +82 -72
  13. package/lib/module/Shared/Components/EIDScanner.js +63 -3
  14. package/lib/module/Shared/Components/FaceCamera.js +73 -6
  15. package/lib/module/Shared/Components/IdentityDocumentCamera.js +9 -4
  16. package/lib/module/Shared/Components/LanguageSelector.js +14 -10
  17. package/lib/module/Shared/Components/NavigationManager.js +4 -2
  18. package/lib/module/Shared/Components/QrCodeScannerCamera.js +6 -1
  19. package/lib/module/Shared/Components/StyledButton.js +108 -9
  20. package/lib/module/Shared/Components/StyledTextInput.js +87 -0
  21. package/lib/module/Shared/Contexts/AppContext.js +3 -1
  22. package/lib/module/Shared/Contexts/ThemeContext.js +40 -0
  23. package/lib/module/Shared/Libs/analytics.utils.js +430 -0
  24. package/lib/module/Shared/Libs/camera.utils.js +58 -2
  25. package/lib/module/Shared/Libs/deeplink.utils.js +8 -0
  26. package/lib/module/Shared/Libs/http-client.js +89 -28
  27. package/lib/module/Shared/Services/AnalyticsService.js +404 -0
  28. package/lib/module/Shared/Types/analytics.types.js +111 -0
  29. package/lib/module/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.js +1 -0
  30. package/lib/module/Translation/index.js +17 -5
  31. package/lib/module/Trustchex.js +52 -16
  32. package/lib/module/index.js +3 -0
  33. package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
  34. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
  35. package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
  36. package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
  37. package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
  38. package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
  39. package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
  40. package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
  41. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
  42. package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
  43. package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
  44. package/lib/typescript/src/Shared/Components/LanguageSelector.d.ts.map +1 -1
  45. package/lib/typescript/src/Shared/Components/NavigationManager.d.ts.map +1 -1
  46. package/lib/typescript/src/Shared/Components/QrCodeScannerCamera.d.ts.map +1 -1
  47. package/lib/typescript/src/Shared/Components/StyledButton.d.ts +12 -2
  48. package/lib/typescript/src/Shared/Components/StyledButton.d.ts.map +1 -1
  49. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts +15 -0
  50. package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts.map +1 -0
  51. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts +2 -0
  52. package/lib/typescript/src/Shared/Contexts/AppContext.d.ts.map +1 -1
  53. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts +26 -0
  54. package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts.map +1 -0
  55. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
  56. package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
  57. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
  58. package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
  59. package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
  60. package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
  61. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
  62. package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
  63. package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
  64. package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
  65. package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
  66. package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
  67. package/lib/typescript/src/Translation/index.d.ts.map +1 -1
  68. package/lib/typescript/src/Trustchex.d.ts +1 -0
  69. package/lib/typescript/src/Trustchex.d.ts.map +1 -1
  70. package/lib/typescript/src/index.d.ts +4 -0
  71. package/lib/typescript/src/index.d.ts.map +1 -1
  72. package/package.json +6 -7
  73. package/src/Screens/Dynamic/ContractAcceptanceScreen.tsx +35 -1
  74. package/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.tsx +30 -0
  75. package/src/Screens/Dynamic/IdentityDocumentScanningScreen.tsx +30 -0
  76. package/src/Screens/Dynamic/LivenessDetectionScreen.tsx +30 -4
  77. package/src/Screens/Static/QrCodeScanningScreen.tsx +12 -2
  78. package/src/Screens/Static/ResultScreen.tsx +79 -4
  79. package/src/Screens/Static/VerificationSessionCheckScreen.tsx +113 -90
  80. package/src/Shared/Components/EIDScanner.tsx +132 -3
  81. package/src/Shared/Components/FaceCamera.tsx +81 -4
  82. package/src/Shared/Components/IdentityDocumentCamera.tsx +8 -6
  83. package/src/Shared/Components/LanguageSelector.tsx +12 -11
  84. package/src/Shared/Components/NavigationManager.tsx +5 -3
  85. package/src/Shared/Components/QrCodeScannerCamera.tsx +5 -1
  86. package/src/Shared/Components/StyledButton.tsx +141 -10
  87. package/src/Shared/Components/StyledTextInput.tsx +128 -0
  88. package/src/Shared/Contexts/AppContext.ts +4 -0
  89. package/src/Shared/Contexts/ThemeContext.tsx +67 -0
  90. package/src/Shared/Libs/analytics.utils.ts +644 -0
  91. package/src/Shared/Libs/camera.utils.ts +74 -2
  92. package/src/Shared/Libs/deeplink.utils.ts +5 -0
  93. package/src/Shared/Libs/http-client.ts +105 -31
  94. package/src/Shared/Services/AnalyticsService.ts +470 -0
  95. package/src/Shared/Types/analytics.types.ts +179 -0
  96. package/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.ts +1 -0
  97. package/src/Translation/Resources/tr.ts +2 -1
  98. package/src/Translation/index.ts +21 -10
  99. package/src/Trustchex.tsx +65 -20
  100. package/src/index.tsx +33 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAE9D,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,aAAa,CAAC;AAEpC,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oCAAoC,CAAC;AACtE,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,qBAAqB,EACrB,YAAY,EACZ,sBAAsB,EACtB,yBAAyB,EACzB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,GACd,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,eAAe,EACf,cAAc,EACd,aAAa,EACb,YAAY,EACZ,kBAAkB,GACnB,MAAM,gCAAgC,CAAC;AAExC,eAAe,SAAS,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trustchex/react-native-sdk",
3
- "version": "1.250.0",
3
+ "version": "1.266.0",
4
4
  "description": "Trustchex mobile app react native SDK for android or ios devices",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -98,7 +98,6 @@
98
98
  "react-native-gesture-handler": "^2.27.1",
99
99
  "react-native-get-random-values": "^1.11.0",
100
100
  "react-native-nfc-manager": "^3.16.2",
101
- "react-native-paper": "^5.14.5",
102
101
  "react-native-safe-area-context": "^5.5.2",
103
102
  "react-native-screens": "^4.13.1",
104
103
  "react-native-svg": "^15.12.0",
@@ -118,7 +117,8 @@
118
117
  "crypto-js": "^4.2.0",
119
118
  "i18next": "^25.3.2",
120
119
  "mrz": "^4.2.1",
121
- "react-i18next": "^15.6.0"
120
+ "react-i18next": "^15.6.0",
121
+ "uuid": "^11.0.3"
122
122
  },
123
123
  "peerDependencies": {
124
124
  "@react-native-community/image-editor": ">=4.3.0",
@@ -135,7 +135,6 @@
135
135
  "react-native-gesture-handler": ">=2.27.1",
136
136
  "react-native-get-random-values": ">=1.11.0",
137
137
  "react-native-nfc-manager": ">=3.16.2",
138
- "react-native-paper": ">=5.14.5",
139
138
  "react-native-safe-area-context": ">=5.5.2",
140
139
  "react-native-screens": ">=4.13.1",
141
140
  "react-native-svg": ">=15.12.0",
@@ -188,9 +187,6 @@
188
187
  "react-native-nfc-manager": {
189
188
  "optional": false
190
189
  },
191
- "react-native-paper": {
192
- "optional": false
193
- },
194
190
  "react-native-safe-area-context": {
195
191
  "optional": false
196
192
  },
@@ -225,6 +221,9 @@
225
221
  "modulePathIgnorePatterns": [
226
222
  "<rootDir>/example/node_modules",
227
223
  "<rootDir>/lib/"
224
+ ],
225
+ "transformIgnorePatterns": [
226
+ "node_modules/(?!(uuid|react-native|@react-native|react-native-device-info|react-native-get-random-values|react-native-fs)/)"
228
227
  ]
229
228
  },
230
229
  "commitlint": {
@@ -15,6 +15,14 @@ import { getI18n, useTranslation } from 'react-i18next';
15
15
  import StyledButton from '../../Shared/Components/StyledButton';
16
16
  import NativeDeviceInfo from '../../Shared/Libs/native-device-info.utils';
17
17
  import { speakWithDebounce } from '../../Shared/Libs/tts.utils';
18
+ import {
19
+ trackFunnelStep,
20
+ useScreenTracking,
21
+ trackConsentGiven,
22
+ trackVerificationStart,
23
+ trackVerificationComplete,
24
+ trackError,
25
+ } from '../../Shared/Libs/analytics.utils';
18
26
 
19
27
  const ContractAcceptanceScreen = () => {
20
28
  const [isEnabled, setIsEnabled] = useState(false);
@@ -26,6 +34,9 @@ const ContractAcceptanceScreen = () => {
26
34
  const atTheEnd = useRef(false);
27
35
  const { t } = useTranslation();
28
36
 
37
+ // Track screen view and exit
38
+ useScreenTracking('contract_acceptance');
39
+
29
40
  useEffect(() => {
30
41
  const contracts = appContext.currentWorkflowStep?.data?.contracts;
31
42
  if (!contracts) {
@@ -56,7 +67,7 @@ const ContractAcceptanceScreen = () => {
56
67
  const paddingToBottom = 10;
57
68
  if (
58
69
  layoutMeasurement.height + contentOffset.y >=
59
- contentSize.height - paddingToBottom &&
70
+ contentSize.height - paddingToBottom &&
60
71
  isReady
61
72
  ) {
62
73
  setIsEnabled(true);
@@ -91,9 +102,23 @@ const ContractAcceptanceScreen = () => {
91
102
  scalesPageToFit={false}
92
103
  scrollEnabled={true}
93
104
  style={styles.webView}
105
+ onError={(syntheticEvent) => {
106
+ const { nativeEvent } = syntheticEvent;
107
+ trackError(
108
+ 'CONTRACT_WEBVIEW_ERROR',
109
+ nativeEvent.description || 'Failed to load contract',
110
+ 'contract_acceptance',
111
+ 'medium',
112
+ { recoverable: true, userAction: 'load_contract' }
113
+ );
114
+ }}
94
115
  onLoadEnd={(event) => {
95
116
  if (event.nativeEvent.url === contractUrl) {
96
117
  setIsReady(true);
118
+
119
+ // Track contract acceptance started
120
+ trackVerificationStart('CONTRACT_ACCEPTANCE');
121
+
97
122
  if (appContext.currentWorkflowStep?.data?.voiceGuidanceActive) {
98
123
  speakWithDebounce(t('termsOfUseAndDataPrivacyScreen.footerText'));
99
124
  }
@@ -128,6 +153,15 @@ const ContractAcceptanceScreen = () => {
128
153
  appContext.identificationInfo.consent = consent;
129
154
  }
130
155
 
156
+ // Track contract acceptance completed
157
+ trackVerificationComplete('CONTRACT_ACCEPTANCE', true, 1);
158
+
159
+ // Track contract acceptance as funnel event
160
+ trackConsentGiven(consent.contractIds, 'contract_acceptance');
161
+
162
+ // Track contract acceptance as funnel step
163
+ trackFunnelStep('Contract Accepted', 1, 5, 'session_start', true);
164
+
131
165
  navigationManagerRef.current?.navigateToNextStep();
132
166
  }}
133
167
  >
@@ -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();