@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.
- 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 +82 -72
- package/lib/module/Shared/Components/EIDScanner.js +63 -3
- package/lib/module/Shared/Components/FaceCamera.js +73 -6
- package/lib/module/Shared/Components/IdentityDocumentCamera.js +9 -4
- package/lib/module/Shared/Components/LanguageSelector.js +14 -10
- package/lib/module/Shared/Components/NavigationManager.js +4 -2
- package/lib/module/Shared/Components/QrCodeScannerCamera.js +6 -1
- package/lib/module/Shared/Components/StyledButton.js +108 -9
- package/lib/module/Shared/Components/StyledTextInput.js +87 -0
- package/lib/module/Shared/Contexts/AppContext.js +3 -1
- package/lib/module/Shared/Contexts/ThemeContext.js +40 -0
- 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 +17 -5
- package/lib/module/Trustchex.js +52 -16
- package/lib/module/index.js +3 -0
- package/lib/typescript/src/Screens/Dynamic/ContractAcceptanceScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentEIDScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/IdentityDocumentScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Dynamic/LivenessDetectionScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/QrCodeScanningScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/ResultScreen.d.ts.map +1 -1
- package/lib/typescript/src/Screens/Static/VerificationSessionCheckScreen.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/EIDScanner.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts +7 -1
- package/lib/typescript/src/Shared/Components/FaceCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/IdentityDocumentCamera.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/LanguageSelector.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/Components/StyledButton.d.ts +12 -2
- package/lib/typescript/src/Shared/Components/StyledButton.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts +15 -0
- package/lib/typescript/src/Shared/Components/StyledTextInput.d.ts.map +1 -0
- 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/Contexts/ThemeContext.d.ts +26 -0
- package/lib/typescript/src/Shared/Contexts/ThemeContext.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts +98 -0
- package/lib/typescript/src/Shared/Libs/analytics.utils.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts +19 -1
- package/lib/typescript/src/Shared/Libs/camera.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/deeplink.utils.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Libs/http-client.d.ts.map +1 -1
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts +86 -0
- package/lib/typescript/src/Shared/Services/AnalyticsService.d.ts.map +1 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts +146 -0
- package/lib/typescript/src/Shared/Types/analytics.types.d.ts.map +1 -0
- package/lib/typescript/src/Shared/VisionCameraPlugins/BarcodeScanner/hooks/useCameraPermissions.d.ts.map +1 -1
- package/lib/typescript/src/Translation/Resources/tr.d.ts.map +1 -1
- package/lib/typescript/src/Translation/index.d.ts.map +1 -1
- package/lib/typescript/src/Trustchex.d.ts +1 -0
- package/lib/typescript/src/Trustchex.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +6 -7
- 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 +113 -90
- package/src/Shared/Components/EIDScanner.tsx +132 -3
- package/src/Shared/Components/FaceCamera.tsx +81 -4
- package/src/Shared/Components/IdentityDocumentCamera.tsx +8 -6
- package/src/Shared/Components/LanguageSelector.tsx +12 -11
- package/src/Shared/Components/NavigationManager.tsx +5 -3
- package/src/Shared/Components/QrCodeScannerCamera.tsx +5 -1
- package/src/Shared/Components/StyledButton.tsx +141 -10
- package/src/Shared/Components/StyledTextInput.tsx +128 -0
- package/src/Shared/Contexts/AppContext.ts +4 -0
- package/src/Shared/Contexts/ThemeContext.tsx +67 -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 +21 -10
- package/src/Trustchex.tsx +65 -20
- 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;
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
174
|
-
//
|
|
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 (
|
|
200
|
-
//
|
|
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.
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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();
|