@sanctum-key/react-native-sdk 1.0.60 → 1.0.62

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 (149) hide show
  1. package/README.md +34 -0
  2. package/android/build/.transforms/ac63f648699cdd4b91e6ee06476f2a70/results.bin +1 -0
  3. package/android/build/.transforms/ac63f648699cdd4b91e6ee06476f2a70/transformed/classes/classes_dex/classes.dex +0 -0
  4. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +9 -0
  5. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  6. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  7. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +45 -25
  8. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +9 -0
  9. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  11. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  12. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  13. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  14. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  15. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  16. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  17. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  18. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
  19. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
  20. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
  21. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
  22. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
  23. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
  24. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
  25. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
  26. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
  27. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
  28. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  54. package/android/build/outputs/logs/manifest-merger-debug-report.txt +34 -12
  55. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  56. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$BarcodeRegion.class +0 -0
  57. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$Companion.class +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$6$1.class → SanctumKeySdkModule$definition$1$7$1.class} +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$6$2.class → SanctumKeySdkModule$definition$1$7$2.class} +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$1.class} +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$5.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$2.class} +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$3.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$3.class} +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$4.class +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$2.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$5.class} +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$6.class +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$4.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$7.class} +0 -0
  67. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$8.class +0 -0
  68. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$6.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$9.class} +0 -0
  69. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$1.class} +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$2.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$2.class} +0 -0
  71. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$3.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$3.class} +0 -0
  72. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$4.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$4.class} +0 -0
  73. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$5.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$5.class} +0 -0
  74. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$6.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$6.class} +0 -0
  75. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$7.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$7.class} +0 -0
  76. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$View$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$View$1.class} +0 -0
  77. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$1.class → SanctumKeySdkModule$definition$lambda$24$lambda$23$$inlined$Prop$1.class} +0 -0
  78. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$2.class → SanctumKeySdkModule$definition$lambda$24$lambda$23$$inlined$Prop$2.class} +0 -0
  79. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule.class +0 -0
  80. package/android/src/main/AndroidManifest.xml +9 -0
  81. package/android/src/main/java/kyc/sanctumkey/com/SanctumKeySdkModule.kt +147 -0
  82. package/build/package.json +1 -1
  83. package/build/src/SanctumKeySdkModule.d.ts +2 -0
  84. package/build/src/SanctumKeySdkModule.d.ts.map +1 -1
  85. package/build/src/SanctumKeySdkModule.js.map +1 -1
  86. package/build/src/SanctumKeySdkModule.web.d.ts +2 -0
  87. package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -1
  88. package/build/src/SanctumKeySdkModule.web.js +14 -0
  89. package/build/src/SanctumKeySdkModule.web.js.map +1 -1
  90. package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
  91. package/build/src/components/KYCElements/CountrySelection.js +1 -1
  92. package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
  93. package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
  94. package/build/src/components/KYCElements/CountrySelectionTemplate.js +27 -17
  95. package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
  96. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  97. package/build/src/components/KYCElements/IDCardCapture.js +46 -28
  98. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  99. package/build/src/components/OverLay/IdCard.d.ts +1 -1
  100. package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
  101. package/build/src/components/OverLay/IdCard.js +47 -14
  102. package/build/src/components/OverLay/IdCard.js.map +1 -1
  103. package/build/src/components/OverLay/type.d.ts +2 -0
  104. package/build/src/components/OverLay/type.d.ts.map +1 -1
  105. package/build/src/components/OverLay/type.js.map +1 -1
  106. package/build/src/components/Svgs/scanningLine.d.ts +4 -1
  107. package/build/src/components/Svgs/scanningLine.d.ts.map +1 -1
  108. package/build/src/components/Svgs/scanningLine.js +15 -11
  109. package/build/src/components/Svgs/scanningLine.js.map +1 -1
  110. package/build/src/components/TemplateKYCExample.d.ts +4 -1
  111. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  112. package/build/src/components/TemplateKYCExample.js +2 -2
  113. package/build/src/components/TemplateKYCExample.js.map +1 -1
  114. package/build/src/components/TemplateKYCFlowRefactored.d.ts +4 -2
  115. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  116. package/build/src/components/TemplateKYCFlowRefactored.js +157 -8
  117. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  118. package/build/src/config/region_mapping.d.ts.map +1 -1
  119. package/build/src/config/region_mapping.js +28 -0
  120. package/build/src/config/region_mapping.js.map +1 -1
  121. package/build/src/config/region_mapping.json +28 -0
  122. package/build/src/index.d.ts +2 -0
  123. package/build/src/index.d.ts.map +1 -1
  124. package/build/src/index.js +2 -0
  125. package/build/src/index.js.map +1 -1
  126. package/build/src/security/deviceSecurity.d.ts +30 -0
  127. package/build/src/security/deviceSecurity.d.ts.map +1 -0
  128. package/build/src/security/deviceSecurity.js +55 -0
  129. package/build/src/security/deviceSecurity.js.map +1 -0
  130. package/build/src/utils/logger.d.ts.map +1 -1
  131. package/build/src/utils/logger.js +5 -0
  132. package/build/src/utils/logger.js.map +1 -1
  133. package/ios/SanctumKeySdkModule.swift +93 -0
  134. package/package.json +1 -1
  135. package/src/SanctumKeySdkModule.ts +2 -0
  136. package/src/SanctumKeySdkModule.web.ts +16 -0
  137. package/src/components/KYCElements/CountrySelection.tsx +3 -1
  138. package/src/components/KYCElements/CountrySelectionTemplate.tsx +73 -24
  139. package/src/components/KYCElements/IDCardCapture.tsx +53 -30
  140. package/src/components/OverLay/IdCard.tsx +72 -15
  141. package/src/components/OverLay/type.ts +4 -2
  142. package/src/components/Svgs/scanningLine.tsx +19 -12
  143. package/src/components/TemplateKYCExample.tsx +8 -1
  144. package/src/components/TemplateKYCFlowRefactored.tsx +202 -16
  145. package/src/config/region_mapping.json +28 -0
  146. package/src/config/region_mapping.ts +28 -0
  147. package/src/index.ts +9 -1
  148. package/src/security/deviceSecurity.ts +106 -0
  149. package/src/utils/logger.ts +5 -0
@@ -1,4 +1,4 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useRef, useState } from 'react';
2
2
  import { View, Text, StyleSheet, SafeAreaView, TouchableOpacity, ActivityIndicator, Dimensions, ScrollView, Platform } from 'react-native';
3
3
  import { KYCTemplate, VerificationState } from '../types/KYC.types';
4
4
  import { TemplateKYCFlowProvider, useTemplateKYCFlowContext } from '../hooks/useTemplateKYCFlow';
@@ -21,8 +21,15 @@ import { AdditionalDocumentsTemplate } from './KYCElements/AdditionalDocumentsTe
21
21
  import { KycEnvironment, BackendEnvironment } from '../types/env.types';
22
22
  import KYCConfig from '../config/KYCConfig';
23
23
  import { logger, sdkLogger } from '../utils/logger';
24
-
25
- interface TemplateKYCFlowProps {
24
+ import {
25
+ checkDeviceSecurity,
26
+ getDeviceSecurityViolations,
27
+ type DeviceSecurityCheckResult,
28
+ type DeviceSecurityPolicy,
29
+ type DeviceSecurityViolation,
30
+ } from '../security/deviceSecurity';
31
+
32
+ export interface TemplateKYCFlowProps {
26
33
  template?: KYCTemplate;
27
34
  templateId?: string;
28
35
  onComplete?: (data: VerificationState) => void;
@@ -40,6 +47,33 @@ interface TemplateKYCFlowProps {
40
47
  onSessionSave?: (sessionId: string) => void;
41
48
  kyc_session_id?: string;
42
49
  templateConfig?: Record<string, string>;
50
+ deviceSecurityPolicy?: DeviceSecurityPolicy;
51
+ onDeviceSecurityCheck?: (result: DeviceSecurityCheckResult) => void;
52
+ }
53
+
54
+ function deviceSecurityErrorMessage(
55
+ violations: DeviceSecurityViolation[],
56
+ language: string,
57
+ ): string {
58
+ const french = language.toLowerCase().startsWith('fr');
59
+ const labels: Record<DeviceSecurityViolation, string> = french
60
+ ? {
61
+ compromised_device: 'appareil rooté ou jailbreaké',
62
+ developer_mode: 'mode développeur ou débogueur activé',
63
+ emulator: 'émulateur ou simulateur détecté',
64
+ check_unavailable: 'vérification de sécurité indisponible',
65
+ }
66
+ : {
67
+ compromised_device: 'rooted or jailbroken device',
68
+ developer_mode: 'developer mode or debugger enabled',
69
+ emulator: 'emulator or simulator detected',
70
+ check_unavailable: 'device security check unavailable',
71
+ };
72
+ const details = violations.map(violation => labels[violation]).join(', ');
73
+
74
+ return french
75
+ ? `La vérification d'identité ne peut pas continuer : ${details}.`
76
+ : `Identity verification cannot continue: ${details}.`;
43
77
  }
44
78
 
45
79
  export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
@@ -60,15 +94,118 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
60
94
  onSessionSave,
61
95
  kyc_session_id,
62
96
  templateConfig,
97
+ deviceSecurityPolicy,
98
+ onDeviceSecurityCheck,
63
99
  }) => {
64
100
  const { t } = useI18n();
65
101
  const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
66
102
  const [resolvedTemplateId, setResolvedTemplateId] = useState<string | undefined>(templateId);
67
103
  const [isResolving, setIsResolving] = useState<boolean>(!templateId && !!templateConfig);
104
+ const securityCheckEnabled = Platform.OS !== 'web' && deviceSecurityPolicy?.enabled !== false;
105
+ const enforceSecurity = env === 'PRODUCTION';
106
+ const blockCompromisedDevices = deviceSecurityPolicy?.blockCompromisedDevices ?? enforceSecurity;
107
+ const blockDeveloperMode = deviceSecurityPolicy?.blockDeveloperMode ?? enforceSecurity;
108
+ const blockEmulators = deviceSecurityPolicy?.blockEmulators ?? enforceSecurity;
109
+ const failClosed = deviceSecurityPolicy?.failClosed ?? enforceSecurity;
110
+ const [securityStatus, setSecurityStatus] = useState<'checking' | 'allowed' | 'blocked'>(
111
+ securityCheckEnabled ? 'checking' : 'allowed',
112
+ );
113
+ const [securityError, setSecurityError] = useState('');
114
+ const [securityRetryToken, setSecurityRetryToken] = useState(0);
115
+ const onErrorRef = useRef(onError);
116
+ const onDeviceSecurityCheckRef = useRef(onDeviceSecurityCheck);
117
+
118
+ useEffect(() => { onErrorRef.current = onError; }, [onError]);
119
+ useEffect(() => { onDeviceSecurityCheckRef.current = onDeviceSecurityCheck; }, [onDeviceSecurityCheck]);
68
120
 
69
121
  // 🚨 1. Determine the active API key based on the environment
70
122
  const activeApiKey = env === 'PRODUCTION' ? API_KEY : (TEST_API_KEY || API_KEY);
71
123
 
124
+ useEffect(() => {
125
+ let cancelled = false;
126
+
127
+ if (!securityCheckEnabled) {
128
+ setSecurityStatus('allowed');
129
+ setSecurityError('');
130
+ return () => { cancelled = true; };
131
+ }
132
+
133
+ setSecurityStatus('checking');
134
+ setSecurityError('');
135
+
136
+ const runSecurityCheck = async () => {
137
+ try {
138
+ const result = await checkDeviceSecurity();
139
+ if (cancelled) return;
140
+
141
+ onDeviceSecurityCheckRef.current?.(result);
142
+ const violations = getDeviceSecurityViolations(result, {
143
+ blockCompromisedDevices,
144
+ blockDeveloperMode,
145
+ blockEmulators,
146
+ failClosed,
147
+ });
148
+
149
+ sdkLogger.info('Device security check completed', {
150
+ platform: result.platform,
151
+ supported: result.supported,
152
+ isCompromised: result.isCompromised,
153
+ isDeveloperModeEnabled: result.isDeveloperModeEnabled,
154
+ isEmulator: result.isEmulator,
155
+ riskSignals: result.riskSignals,
156
+ blocked: violations.length > 0,
157
+ });
158
+
159
+ if (violations.length > 0) {
160
+ const message = deviceSecurityErrorMessage(violations, language);
161
+ setSecurityError(message);
162
+ setSecurityStatus('blocked');
163
+ onErrorRef.current?.(message);
164
+ return;
165
+ }
166
+
167
+ setSecurityStatus('allowed');
168
+ } catch (error: any) {
169
+ if (cancelled) return;
170
+ const unavailableResult: DeviceSecurityCheckResult = {
171
+ platform: Platform.OS === 'android' || Platform.OS === 'ios' ? Platform.OS : 'unknown',
172
+ supported: false,
173
+ isRooted: false,
174
+ isJailbroken: false,
175
+ isEmulator: false,
176
+ isDeveloperModeEnabled: false,
177
+ developerModeCheckSupported: false,
178
+ isDebuggerAttached: false,
179
+ isCompromised: false,
180
+ riskSignals: ['native_device_security_check_failed'],
181
+ checkedAt: new Date().toISOString(),
182
+ };
183
+ onDeviceSecurityCheckRef.current?.(unavailableResult);
184
+ sdkLogger.error('Device security check failed', { error: error?.message });
185
+
186
+ if (failClosed) {
187
+ const message = deviceSecurityErrorMessage(['check_unavailable'], language);
188
+ setSecurityError(message);
189
+ setSecurityStatus('blocked');
190
+ onErrorRef.current?.(message);
191
+ } else {
192
+ setSecurityStatus('allowed');
193
+ }
194
+ }
195
+ };
196
+
197
+ void runSecurityCheck();
198
+ return () => { cancelled = true; };
199
+ }, [
200
+ securityCheckEnabled,
201
+ blockCompromisedDevices,
202
+ blockDeveloperMode,
203
+ blockEmulators,
204
+ failClosed,
205
+ securityRetryToken,
206
+ language,
207
+ ]);
208
+
72
209
  useEffect(() => {
73
210
  if (activeApiKey) {
74
211
  if (sdkLogger.setApiKey) sdkLogger.setApiKey(activeApiKey);
@@ -83,6 +220,8 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
83
220
 
84
221
 
85
222
  useEffect(() => {
223
+ if (securityStatus !== 'allowed') return;
224
+
86
225
  // If a strict templateId is provided, use it immediately and skip geolocation
87
226
  if (templateId) {
88
227
  setResolvedTemplateId(templateId);
@@ -125,12 +264,12 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
125
264
  };
126
265
 
127
266
  fetchLocationAndRoute();
128
- }, [templateId, templateConfig]); // Only re-run if the props change
267
+ }, [templateId, templateConfig, securityStatus]); // Only re-run if the props change
129
268
 
130
269
 
131
270
 
132
271
  useEffect(() => {
133
- if (isResolving) return;
272
+ if (isResolving || securityStatus !== 'allowed') return;
134
273
 
135
274
  // Updated error check to include templateConfig
136
275
  if (!providedTemplate && !resolvedTemplateId) {
@@ -148,7 +287,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
148
287
  logger.log(`Fetching template ${resolvedTemplateId} via API Key`);
149
288
  loadTemplate(resolvedTemplateId, activeApiKey);
150
289
  }
151
- }, [resolvedTemplateId, activeApiKey, providedTemplate, loadTemplate, onError, isResolving]);
290
+ }, [resolvedTemplateId, activeApiKey, providedTemplate, loadTemplate, onError, isResolving, securityStatus]);
152
291
 
153
292
  useEffect(() => {
154
293
  if (error) {
@@ -180,7 +319,44 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
180
319
 
181
320
  const template = providedTemplate || loadedTemplate;
182
321
 
183
-
322
+ if (securityStatus === 'checking') {
323
+ return (
324
+ <View style={styles.loadingContainer}>
325
+ <ActivityIndicator size="large" color="#2DBD60" />
326
+ <Text style={styles.loadingText}>
327
+ {language.toLowerCase().startsWith('fr')
328
+ ? 'Vérification de la sécurité de l’appareil…'
329
+ : 'Checking device security…'}
330
+ </Text>
331
+ </View>
332
+ );
333
+ }
334
+
335
+ if (securityStatus === 'blocked') {
336
+ return (
337
+ <View style={styles.errorContainer}>
338
+ <Text style={styles.securityTitle}>
339
+ {language.toLowerCase().startsWith('fr') ? 'Appareil non sécurisé' : 'Device security issue'}
340
+ </Text>
341
+ <Text style={styles.errorText}>{securityError}</Text>
342
+ <TouchableOpacity
343
+ style={styles.retryButton}
344
+ onPress={() => setSecurityRetryToken(value => value + 1)}
345
+ >
346
+ <Text style={styles.retryButtonText}>
347
+ {language.toLowerCase().startsWith('fr') ? 'Réessayer' : 'Retry'}
348
+ </Text>
349
+ </TouchableOpacity>
350
+ {onCancel ? (
351
+ <TouchableOpacity style={styles.cancelSecurityButton} onPress={onCancel}>
352
+ <Text style={styles.cancelSecurityButtonText}>
353
+ {language.toLowerCase().startsWith('fr') ? 'Annuler' : 'Cancel'}
354
+ </Text>
355
+ </TouchableOpacity>
356
+ ) : null}
357
+ </View>
358
+ );
359
+ }
184
360
 
185
361
  if (templateId && isLoading && !template) {
186
362
  return (
@@ -352,14 +528,20 @@ const TemplateKYCFlowContent: React.FC<{ onCancel?: () => void }> = ({ onCancel
352
528
  </View>
353
529
  ) : null}
354
530
 
355
- <ScrollView
356
- style={styles.scrollView}
357
- contentContainerStyle={styles.content}
358
- showsVerticalScrollIndicator={Platform.OS === 'web'}
359
- bounces={false}
360
- >
361
- {renderCurrentComponent()}
362
- </ScrollView>
531
+ {currentComponent.type === 'country_selection' ? (
532
+ <View style={styles.selfScrollingContent}>
533
+ {renderCurrentComponent()}
534
+ </View>
535
+ ) : (
536
+ <ScrollView
537
+ style={styles.scrollView}
538
+ contentContainerStyle={styles.content}
539
+ showsVerticalScrollIndicator={Platform.OS === 'web'}
540
+ bounces={false}
541
+ >
542
+ {renderCurrentComponent()}
543
+ </ScrollView>
544
+ )}
363
545
 
364
546
  {state.isProcessing && (
365
547
  <View style={styles.processingContainer}>
@@ -386,6 +568,7 @@ const styles = StyleSheet.create({
386
568
  stepDescription: { fontSize: 14, color: '#666', lineHeight: 20, },
387
569
  scrollView: { flex: 1, },
388
570
  content: { flexGrow: 1, alignItems: 'center', paddingBottom: 20, },
571
+ selfScrollingContent: { flex: 1, minHeight: 0, alignItems: 'center', paddingBottom: 20, },
389
572
  navigation: { flexDirection: 'row', justifyContent: 'space-between', padding: 20, backgroundColor: 'white', borderTopWidth: 1, borderTopColor: '#e0e0e0', },
390
573
  navButton: { paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, minWidth: 100, alignItems: 'center', },
391
574
  primaryButton: { backgroundColor: '#2DBD60', },
@@ -400,4 +583,7 @@ const styles = StyleSheet.create({
400
583
  errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5', padding: 20, },
401
584
  retryButton: { marginTop: 20, paddingHorizontal: 24, paddingVertical: 12, backgroundColor: '#2DBD60', borderRadius: 8, },
402
585
  retryButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', },
403
- });
586
+ securityTitle: { color: '#991b1b', fontSize: 22, fontWeight: '700', textAlign: 'center', },
587
+ cancelSecurityButton: { marginTop: 12, paddingHorizontal: 24, paddingVertical: 12, },
588
+ cancelSecurityButtonText: { color: '#4b5563', fontSize: 16, fontWeight: '600', },
589
+ });
@@ -1,4 +1,20 @@
1
1
  {
2
+ "Australia": {
3
+ "passport": {
4
+ "root": [
5
+ {
6
+ "py_file": "australia_pp_01.py",
7
+ "front": "MRZ",
8
+ "mrz_type": "TD3"
9
+ },
10
+ {
11
+ "py_file": "australia_pp_02.py",
12
+ "front": "MRZ",
13
+ "mrz_type": "TD3"
14
+ }
15
+ ]
16
+ }
17
+ },
2
18
  "Belgium": {
3
19
  "passport": {
4
20
  "root": [
@@ -578,6 +594,18 @@
578
594
  }
579
595
  },
580
596
  "Kenya": {
597
+ "drivers_licence": {
598
+ "root": [
599
+ {
600
+ "py_file": "dl_back.py",
601
+ "keys_found": null
602
+ },
603
+ {
604
+ "py_file": "dl_front.py",
605
+ "keys_found": null
606
+ }
607
+ ]
608
+ },
581
609
  "identity_card": {
582
610
  "root": [
583
611
  {
@@ -2,6 +2,22 @@ import { ICountryMapping } from "../types/KYC.types";
2
2
 
3
3
  export const countryMapping: Record<string, Record<string, Record<string, ICountryMapping[]>>> =
4
4
  {
5
+ "AU": {
6
+ "passport": {
7
+ "root": [
8
+ {
9
+ "py_file": "australia_pp_01.py",
10
+ "front": "MRZ",
11
+ "mrz_type": "TD3"
12
+ },
13
+ {
14
+ "py_file": "australia_pp_02.py",
15
+ "front": "MRZ",
16
+ "mrz_type": "TD3"
17
+ }
18
+ ]
19
+ }
20
+ },
5
21
  "BE": {
6
22
  "passport": {
7
23
  "root": [
@@ -592,6 +608,18 @@ export const countryMapping: Record<string, Record<string, Record<string, ICount
592
608
  }
593
609
  },
594
610
  "KE": {
611
+ "drivers_licence": {
612
+ "root": [
613
+ {
614
+ "py_file": "dl_back.py",
615
+ "keys_found": null
616
+ },
617
+ {
618
+ "py_file": "dl_front.py",
619
+ "keys_found": null
620
+ }
621
+ ]
622
+ },
595
623
  "identity_card": {
596
624
  "root": [
597
625
  {
package/src/index.ts CHANGED
@@ -25,5 +25,13 @@ export { WelcomeTemplate } from './components/KYCElements/WelcomeTemplate';
25
25
  export { EnhancedCameraView } from './components/EnhancedCameraView';
26
26
  export { UnifiedCameraModule } from './modules/camera';
27
27
 
28
+ // Export native device-integrity checks and policy types.
29
+ export { checkDeviceSecurity, getDeviceSecurityViolations } from './security/deviceSecurity';
30
+ export type {
31
+ DeviceSecurityCheckResult,
32
+ DeviceSecurityPolicy,
33
+ DeviceSecurityViolation,
34
+ } from './security/deviceSecurity';
35
+
28
36
  // Export Web KYC Components
29
- export { default as WebKYCEntry } from './web/WebKYCEntry';
37
+ export { default as WebKYCEntry } from './web/WebKYCEntry';
@@ -0,0 +1,106 @@
1
+ import { Platform } from 'react-native';
2
+ import SanctumKeySdkModule from '../SanctumKeySdkModule';
3
+
4
+ export type DeviceSecurityPlatform = 'android' | 'ios' | 'web' | 'unknown';
5
+
6
+ export interface DeviceSecurityCheckResult {
7
+ platform: DeviceSecurityPlatform;
8
+ supported: boolean;
9
+ isRooted: boolean;
10
+ isJailbroken: boolean;
11
+ isEmulator: boolean;
12
+ isDeveloperModeEnabled: boolean;
13
+ developerModeCheckSupported: boolean;
14
+ isDebuggerAttached: boolean;
15
+ isCompromised: boolean;
16
+ riskSignals: string[];
17
+ checkedAt: string;
18
+ }
19
+
20
+ export interface DeviceSecurityPolicy {
21
+ /** Run the check. Defaults to true on Android and iOS. */
22
+ enabled?: boolean;
23
+ /** Block rooted Android or jailbroken iOS devices. Defaults to true in production. */
24
+ blockCompromisedDevices?: boolean;
25
+ /** Block Android developer options or an attached iOS debugger. Defaults to true in production. */
26
+ blockDeveloperMode?: boolean;
27
+ /** Block Android emulators and the iOS Simulator. Defaults to true in production. */
28
+ blockEmulators?: boolean;
29
+ /** Block when the native check is unavailable or fails. Defaults to true in production. */
30
+ failClosed?: boolean;
31
+ }
32
+
33
+ export type DeviceSecurityViolation =
34
+ | 'compromised_device'
35
+ | 'developer_mode'
36
+ | 'emulator'
37
+ | 'check_unavailable';
38
+
39
+ type NativeDeviceSecurityResult = Partial<Omit<DeviceSecurityCheckResult, 'checkedAt'>>;
40
+
41
+ const booleanValue = (value: unknown): boolean => value === true;
42
+
43
+ const stringArray = (value: unknown): string[] => (
44
+ Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
45
+ );
46
+
47
+ export async function checkDeviceSecurity(): Promise<DeviceSecurityCheckResult> {
48
+ if (Platform.OS === 'web') {
49
+ return {
50
+ platform: 'web',
51
+ supported: false,
52
+ isRooted: false,
53
+ isJailbroken: false,
54
+ isEmulator: false,
55
+ isDeveloperModeEnabled: false,
56
+ developerModeCheckSupported: false,
57
+ isDebuggerAttached: false,
58
+ isCompromised: false,
59
+ riskSignals: ['native_device_security_check_unavailable_on_web'],
60
+ checkedAt: new Date().toISOString(),
61
+ };
62
+ }
63
+
64
+ if (typeof SanctumKeySdkModule.checkDeviceSecurity !== 'function') {
65
+ throw new Error(
66
+ 'The native device-security checker is unavailable. Rebuild the Android or iOS application with the latest SanctumKey SDK.',
67
+ );
68
+ }
69
+
70
+ const nativeResult = await SanctumKeySdkModule.checkDeviceSecurity() as NativeDeviceSecurityResult;
71
+ const isRooted = booleanValue(nativeResult.isRooted);
72
+ const isJailbroken = booleanValue(nativeResult.isJailbroken);
73
+
74
+ return {
75
+ platform: nativeResult.platform === 'android' || nativeResult.platform === 'ios'
76
+ ? nativeResult.platform
77
+ : 'unknown',
78
+ supported: nativeResult.supported !== false,
79
+ isRooted,
80
+ isJailbroken,
81
+ isEmulator: booleanValue(nativeResult.isEmulator),
82
+ isDeveloperModeEnabled: booleanValue(nativeResult.isDeveloperModeEnabled),
83
+ developerModeCheckSupported: booleanValue(nativeResult.developerModeCheckSupported),
84
+ isDebuggerAttached: booleanValue(nativeResult.isDebuggerAttached),
85
+ isCompromised: booleanValue(nativeResult.isCompromised) || isRooted || isJailbroken,
86
+ riskSignals: stringArray(nativeResult.riskSignals),
87
+ checkedAt: new Date().toISOString(),
88
+ };
89
+ }
90
+
91
+ export function getDeviceSecurityViolations(
92
+ result: DeviceSecurityCheckResult,
93
+ policy: Required<Pick<
94
+ DeviceSecurityPolicy,
95
+ 'blockCompromisedDevices' | 'blockDeveloperMode' | 'blockEmulators' | 'failClosed'
96
+ >>,
97
+ ): DeviceSecurityViolation[] {
98
+ const violations: DeviceSecurityViolation[] = [];
99
+
100
+ if (!result.supported && policy.failClosed) violations.push('check_unavailable');
101
+ if (result.isCompromised && policy.blockCompromisedDevices) violations.push('compromised_device');
102
+ if (result.isDeveloperModeEnabled && policy.blockDeveloperMode) violations.push('developer_mode');
103
+ if (result.isEmulator && policy.blockEmulators) violations.push('emulator');
104
+
105
+ return violations;
106
+ }
@@ -1,5 +1,6 @@
1
1
  import { Platform, AppState, AppStateStatus } from 'react-native';
2
2
  import PostHog from 'posthog-react-native';
3
+ import AsyncStorage from '@react-native-async-storage/async-storage';
3
4
 
4
5
  const posthogClient = new PostHog('phc_qjCHjCgkdVniammXrrfLrmNPoPH5uAmkzoQMhrEhGaYR', {
5
6
  host: 'https://eu.i.posthog.com',
@@ -7,6 +8,10 @@ const posthogClient = new PostHog('phc_qjCHjCgkdVniammXrrfLrmNPoPH5uAmkzoQMhrEhG
7
8
  captureAppLifecycleEvents: false,
8
9
  flushAt: 1,
9
10
  flushInterval: 1000, // Backup: flush automatically every 1 second
11
+ customStorage: {
12
+ getItem: async (key) => await AsyncStorage.getItem(key),
13
+ setItem: async (key, value) => await AsyncStorage.setItem(key, value),
14
+ }
10
15
  });
11
16
 
12
17
  const isProduction = (): boolean => {