@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
@@ -1,6 +1,7 @@
1
1
  import React, { useState, useEffect, useCallback } from 'react';
2
2
  import { Alert, View, Text, Image, StyleSheet } from 'react-native';
3
3
  import NFCManager from 'react-native-nfc-manager';
4
+ import DeviceInfo from 'react-native-device-info';
4
5
  import { MRZInfo } from '../EIDReader/lds/icao/mrzInfo';
5
6
  import { eidReader } from '../EIDReader/eidReader';
6
7
  import NativeProgressBar from './NativeProgressBar';
@@ -11,6 +12,11 @@ import StyledButton from './StyledButton';
11
12
  import LottieView from 'lottie-react-native';
12
13
  import { useKeepAwake } from '../Libs/native-keep-awake.utils';
13
14
  import { speakWithDebounce } from '../Libs/tts.utils';
15
+ import {
16
+ trackEIDScanStart,
17
+ trackEIDScanComplete,
18
+ trackEIDScanFailed,
19
+ } from '../Libs/analytics.utils';
14
20
 
15
21
  interface eIDScannerProps {
16
22
  documentNumber: string;
@@ -47,12 +53,58 @@ const EIDScanner = ({
47
53
  const { t } = useTranslation();
48
54
  const appContext = React.useContext(AppContext);
49
55
  const [voiceGuidanceMessage, setVoiceGuidanceMessage] = useState<string>();
56
+ const [attemptNumber, setAttemptNumber] = useState<number>(0);
50
57
 
51
58
  const readNFC = useCallback(async () => {
59
+ const startTime = Date.now();
60
+ const currentAttempt = attemptNumber + 1;
61
+ setAttemptNumber(currentAttempt);
62
+
52
63
  setIsScanning(true);
53
64
  setIsScanned(false);
54
65
  setProgress(0);
55
66
 
67
+ // Track EID scan start using analytics helper
68
+ const docType =
69
+ (documentType as 'ID' | 'PASSPORT' | 'UNKNOWN') || 'UNKNOWN';
70
+
71
+ // If device doesn't support NFC or NFC is disabled, track and abort early
72
+ if (!hasNfc) {
73
+ await trackEIDScanFailed(
74
+ docType,
75
+ 'device_unsupported',
76
+ 'Device does not support NFC',
77
+ 0,
78
+ currentAttempt,
79
+ DeviceInfo.getModel(),
80
+ DeviceInfo.getSystemVersion()
81
+ ).catch(() => {});
82
+
83
+ // Stop scanning - user will see the not-supported UI
84
+ setIsScanning(false);
85
+ setProgress(0);
86
+ return;
87
+ }
88
+
89
+ if (!isEnabled) {
90
+ await trackEIDScanFailed(
91
+ docType,
92
+ 'not_enabled',
93
+ 'NFC is disabled on device',
94
+ 0,
95
+ currentAttempt,
96
+ DeviceInfo.getModel(),
97
+ DeviceInfo.getSystemVersion()
98
+ ).catch(() => {});
99
+
100
+ // Stop scanning - allow the UI to prompt user to enable NFC
101
+ setIsScanning(false);
102
+ setProgress(0);
103
+ return;
104
+ }
105
+
106
+ await trackEIDScanStart(docType, hasNfc, isEnabled, currentAttempt).catch(()=>{});
107
+
56
108
  try {
57
109
  if (documentNumber && dateOfBirth && dateOfExpiry) {
58
110
  const passportData = await eidReader(
@@ -64,23 +116,95 @@ const EIDScanner = ({
64
116
  }
65
117
  );
66
118
  if (passportData) {
119
+ const scanDuration = Date.now() - startTime;
67
120
  setDocumentFaceImage(passportData.faceImage);
68
121
  setDocumentMRZInfo(passportData.mrz);
69
122
  setDocumentFaceImageMimeType(passportData.mimeType);
70
123
  setIsScanned(true);
124
+
125
+ // Track successful EID scan
126
+ await trackEIDScanComplete(docType, scanDuration, currentAttempt).catch(()=>{});
71
127
  }
72
128
  } else {
129
+ const scanDuration = Date.now() - startTime;
130
+ // MRZ input issue — track as NFC scan failure (unknown error type for NFC-specific stream)
131
+ await trackEIDScanFailed(
132
+ docType,
133
+ 'unknown',
134
+ 'Invalid MRZ fields',
135
+ scanDuration,
136
+ currentAttempt,
137
+ DeviceInfo.getModel(),
138
+ DeviceInfo.getSystemVersion()
139
+ ).catch(() => {});
73
140
  Alert.alert(t('general.error'), t('eidScannerScreen.invalidMRZFields'));
74
141
  }
75
142
  } catch (error) {
76
- console.debug(error);
77
- // Ignore error
143
+ const scanDuration = Date.now() - startTime;
144
+ const errorMessage =
145
+ error instanceof Error ? error.message : 'Unknown error';
146
+
147
+ // Determine error type
148
+ let errorType:
149
+ | 'device_unsupported'
150
+ | 'not_enabled'
151
+ | 'reading_error'
152
+ | 'user_cancelled'
153
+ | 'timeout'
154
+ | 'unknown' = 'unknown';
155
+
156
+ if (
157
+ errorMessage.includes('cancelled') ||
158
+ errorMessage.includes('cancel')
159
+ ) {
160
+ errorType = 'user_cancelled';
161
+ } else if (
162
+ errorMessage.includes('timeout') ||
163
+ errorMessage.includes('time out')
164
+ ) {
165
+ errorType = 'timeout';
166
+ } else if (errorMessage.includes('not supported')) {
167
+ errorType = 'device_unsupported';
168
+ } else if (
169
+ errorMessage.includes('not enabled') ||
170
+ errorMessage.includes('disabled')
171
+ ) {
172
+ errorType = 'not_enabled';
173
+ } else if (
174
+ errorMessage.includes('read') ||
175
+ errorMessage.includes('communication')
176
+ ) {
177
+ errorType = 'reading_error';
178
+ }
179
+
180
+ // Track EID scan failure with detailed metadata
181
+ await trackEIDScanFailed(
182
+ docType,
183
+ errorType,
184
+ errorMessage,
185
+ scanDuration,
186
+ currentAttempt,
187
+ DeviceInfo.getModel(),
188
+ DeviceInfo.getSystemVersion()
189
+ ).catch(() => {});
190
+
191
+ console.debug('NFC scan error:', error);
192
+ // Ignore error - let user retry
78
193
  } finally {
79
194
  setIsScanning(false);
80
195
  setProgress(0);
81
196
  setHasGuideShown(false);
82
197
  }
83
- }, [documentNumber, dateOfBirth, dateOfExpiry, t]);
198
+ }, [
199
+ documentNumber,
200
+ dateOfBirth,
201
+ dateOfExpiry,
202
+ documentType,
203
+ hasNfc,
204
+ isEnabled,
205
+ attemptNumber,
206
+ t,
207
+ ]);
84
208
 
85
209
  const getFieldsFromMRZ = useCallback((mrz: MRZInfo) => {
86
210
  return {
@@ -103,8 +227,13 @@ const EIDScanner = ({
103
227
  setHasNFC(deviceIsSupported);
104
228
  isNFCSupported && isNFCSupported(deviceIsSupported);
105
229
 
230
+ // NFC not supported is a device limitation - not actionable by developers
231
+
106
232
  const deviceIsEnabled = await NFCManager.isEnabled();
107
233
  setIsEnabled(deviceIsEnabled);
234
+
235
+ // NFC disabled is a user setting - they can enable it in settings
236
+ // Not tracking as it's user-controllable
108
237
  };
109
238
 
110
239
  checkIsSupported();
@@ -9,6 +9,7 @@ import {
9
9
  Linking,
10
10
  Dimensions,
11
11
  ActivityIndicator,
12
+ NativeModules,
12
13
  } from 'react-native';
13
14
  import {
14
15
  useCameraDevice,
@@ -27,7 +28,7 @@ import {
27
28
  } from '../VisionCameraPlugins/FaceDetector';
28
29
  import { Worklets, useSharedValue } from 'react-native-worklets-core';
29
30
  import { crop } from '../VisionCameraPlugins/Cropper';
30
- import { isFrameBright } from '../Libs/camera.utils';
31
+ import { isCircularRegionBright } from '../Libs/camera.utils';
31
32
  import { useTranslation } from 'react-i18next';
32
33
  import StyledButton from './StyledButton';
33
34
  import { useTheme } from '../Contexts/ThemeContext';
@@ -39,6 +40,12 @@ export type FaceCameraProps = {
39
40
  isImageBright: boolean
40
41
  ) => void;
41
42
  onCameraInitialized: (camera: Camera) => void;
43
+ previewRect?: {
44
+ minX: number;
45
+ minY: number;
46
+ width: number;
47
+ height: number;
48
+ };
42
49
  };
43
50
 
44
51
  const VIDEO_WIDTH = 1280;
@@ -50,6 +57,7 @@ const windowHeight = Dimensions.get('window').height;
50
57
  const FaceCamera = ({
51
58
  onFacesDetected,
52
59
  onCameraInitialized,
60
+ previewRect,
53
61
  }: FaceCameraProps) => {
54
62
  useKeepAwake();
55
63
  const theme = useTheme();
@@ -115,10 +123,74 @@ const FaceCamera = ({
115
123
  };
116
124
  }, [device, format, isFocused]);
117
125
 
126
+ // Set screen brightness to maximum when camera is active
127
+ useEffect(() => {
128
+ const { DeviceBrightness } = NativeModules;
129
+ if (!DeviceBrightness) return;
130
+
131
+ let originalBrightness = -1;
132
+
133
+ const setBrightness = async () => {
134
+ if (isActive) {
135
+ try {
136
+ originalBrightness = await DeviceBrightness.getBrightness();
137
+ await DeviceBrightness.setBrightness(1.0);
138
+ } catch (error) {
139
+ console.log('Failed to set brightness:', error);
140
+ }
141
+ }
142
+ };
143
+
144
+ setBrightness();
145
+
146
+ return () => {
147
+ if (originalBrightness >= 0) {
148
+ DeviceBrightness.setBrightness(originalBrightness).catch(() => { });
149
+ }
150
+ };
151
+ }, [isActive]);
152
+
118
153
  const handleWorklet = (frame: Frame) => {
119
154
  'worklet';
120
155
  try {
121
- const isBright = isFrameBright(frame);
156
+ // Calculate brightness based on the circular preview area if provided
157
+ // Otherwise fall back to entire frame brightness
158
+ let isBright = false;
159
+ if (previewRect) {
160
+ // Convert preview rect from screen coordinates to frame coordinates
161
+ const scaleX = frame.width / windowWidth;
162
+ const scaleY = frame.height / windowHeight;
163
+ const frameCircleRect = {
164
+ minX: Math.floor(previewRect.minX * scaleX),
165
+ minY: Math.floor(previewRect.minY * scaleY),
166
+ width: Math.floor(previewRect.width * scaleX),
167
+ height: Math.floor(previewRect.height * scaleY),
168
+ };
169
+ isBright = isCircularRegionBright(frame, frameCircleRect, 60);
170
+ } else {
171
+ // Fallback: check entire frame brightness (use legacy method)
172
+ const buffer = frame.toArrayBuffer();
173
+ const data = new Uint8Array(buffer);
174
+ const width = frame.width;
175
+ const height = frame.height;
176
+ let luminanceSum = 0;
177
+ let pixelCount = 0;
178
+ const centerX = Math.floor(width / 2);
179
+ const centerY = Math.floor(height / 2);
180
+ const halfSizeX = Math.floor(width / 2);
181
+ const halfSizeY = Math.floor(height / 2);
182
+
183
+ for (let y = centerY - halfSizeY; y < centerY + halfSizeY; y++) {
184
+ for (let x = centerX - halfSizeX; x < centerX + halfSizeX; x++) {
185
+ const index = y * width + x;
186
+ if (data[index] !== undefined) {
187
+ luminanceSum += data[index];
188
+ pixelCount++;
189
+ }
190
+ }
191
+ }
192
+ isBright = (luminanceSum / pixelCount) > 60;
193
+ }
122
194
 
123
195
  const image = crop(frame, {
124
196
  cropRegion: {
@@ -172,6 +244,7 @@ const FaceCamera = ({
172
244
  }
173
245
 
174
246
  if (!cameraPermission.hasPermission) {
247
+ // Camera permission denied by user - their choice, not actionable
175
248
  return (
176
249
  <View style={styles.permissionContainer}>
177
250
  <Text style={styles.permissionText}>
@@ -190,6 +263,7 @@ const FaceCamera = ({
190
263
  }
191
264
 
192
265
  if (!microphonePermission.hasPermission) {
266
+ // Microphone permission denied by user - their choice, not actionable
193
267
  return (
194
268
  <View style={styles.permissionContainer}>
195
269
  <Text style={styles.permissionText}>
@@ -208,6 +282,7 @@ const FaceCamera = ({
208
282
  }
209
283
 
210
284
  if (device == null) {
285
+ // No camera device - device limitation, not actionable
211
286
  return (
212
287
  <View style={styles.permissionContainer}>
213
288
  <Text style={styles.permissionText}>
@@ -506,10 +506,10 @@ const IdentityDocumentCamera = ({
506
506
  face.bounds.x >= width ||
507
507
  face.bounds.y >= height
508
508
  ) {
509
- console.warn(
510
- 'Invalid face bounds detected, skipping face:',
511
- face.bounds
512
- );
509
+ // console.warn(
510
+ // 'Invalid face bounds detected, skipping face:',
511
+ // face.bounds
512
+ // );
513
513
  continue;
514
514
  }
515
515
 
@@ -15,6 +15,7 @@ import { View, StyleSheet, Alert } from 'react-native';
15
15
  import { useTranslation } from 'react-i18next';
16
16
  import i18n from '../../Translation';
17
17
  import StyledButton from './StyledButton';
18
+ import { analyticsService } from '../Services/AnalyticsService';
18
19
 
19
20
  // Simple global navigation lock
20
21
  let isNavigating = false;
@@ -187,6 +188,7 @@ const NavigationManager = forwardRef(
187
188
  appContext.currentWorkflowStep = undefined;
188
189
  appContext.workflowSteps = undefined;
189
190
  appContext.isDemoSession = false;
191
+ analyticsService.setDemoSession(false);
190
192
  appContext.identificationInfo = {
191
193
  sessionId: '',
192
194
  identificationId: '',
@@ -172,6 +172,7 @@ const QrCodeScannerCamera = ({ onQrCodeScanned }: QrCodeScannerCameraProps) => {
172
172
  }
173
173
 
174
174
  if (!cameraPermission.hasPermission) {
175
+ // Camera permission denied by user - their choice, not actionable
175
176
  return (
176
177
  <View style={styles.permissionContainer}>
177
178
  <Text style={styles.permissionText}>
@@ -190,6 +191,7 @@ const QrCodeScannerCamera = ({ onQrCodeScanned }: QrCodeScannerCameraProps) => {
190
191
  }
191
192
 
192
193
  if (device == null) {
194
+ // No camera device - device limitation, not actionable
193
195
  return (
194
196
  <View style={styles.permissionContainer}>
195
197
  <TextView style={styles.permissionText}>
@@ -19,6 +19,8 @@ export type AppContextType = {
19
19
  currentWorkflowStep?: WorkflowStep;
20
20
  onCompleted?: () => void;
21
21
  onError?: (error: string) => void;
22
+ setSessionId?: (id: string) => void;
23
+ setBaseUrl?: (url: string) => void;
22
24
  };
23
25
 
24
26
  export default createContext<AppContextType>({
@@ -44,4 +46,6 @@ export default createContext<AppContextType>({
44
46
  currentWorkflowStep: undefined,
45
47
  onCompleted: undefined,
46
48
  onError: undefined,
49
+ setSessionId: undefined,
50
+ setBaseUrl: undefined,
47
51
  });