@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
@@ -8,6 +8,9 @@ import android.graphics.BitmapFactory
8
8
  import android.graphics.Color
9
9
  import android.graphics.Matrix
10
10
  import android.net.Uri
11
+ import android.os.Build
12
+ import android.os.Debug
13
+ import android.provider.Settings
11
14
  import android.view.Gravity
12
15
  import android.view.View
13
16
  import android.widget.FrameLayout
@@ -39,6 +42,7 @@ import expo.modules.kotlin.viewevent.EventDispatcher
39
42
  import expo.modules.kotlin.views.ExpoView
40
43
  import java.io.FileInputStream
41
44
  import java.io.InputStream
45
+ import java.io.File
42
46
  import java.util.EnumMap
43
47
  import java.util.concurrent.TimeUnit
44
48
  import kotlin.math.ceil
@@ -440,6 +444,137 @@ class SanctumKeySdkModule : Module() {
440
444
  ?: decodeBarcodeWithZxing(imagePath, requestedFormats)
441
445
  }
442
446
 
447
+ private fun isProbablyEmulator(): Boolean {
448
+ val fingerprint = Build.FINGERPRINT.lowercase()
449
+ val model = Build.MODEL.lowercase()
450
+ val manufacturer = Build.MANUFACTURER.lowercase()
451
+ val brand = Build.BRAND.lowercase()
452
+ val device = Build.DEVICE.lowercase()
453
+ val product = Build.PRODUCT.lowercase()
454
+ val hardware = Build.HARDWARE.lowercase()
455
+ val board = Build.BOARD.lowercase()
456
+
457
+ return fingerprint.startsWith("generic") ||
458
+ fingerprint.startsWith("unknown") ||
459
+ fingerprint.contains("emulator") ||
460
+ fingerprint.contains("test-keys") && model.contains("sdk") ||
461
+ model.contains("google_sdk") ||
462
+ model.contains("emulator") ||
463
+ model.contains("android sdk built for") ||
464
+ manufacturer.contains("genymotion") ||
465
+ hardware.contains("goldfish") ||
466
+ hardware.contains("ranchu") ||
467
+ hardware.contains("vbox86") ||
468
+ board.contains("goldfish") ||
469
+ product.contains("sdk_gphone") ||
470
+ product.contains("google_sdk") ||
471
+ product.contains("sdk_x86") ||
472
+ product.contains("vbox86p") ||
473
+ (brand.startsWith("generic") && device.startsWith("generic"))
474
+ }
475
+
476
+ private fun rootSignals(context: Context): List<String> {
477
+ val signals = mutableSetOf<String>()
478
+ val tags = Build.TAGS.orEmpty()
479
+ if (tags.contains("test-keys")) signals.add("android_build_test_keys")
480
+
481
+ val rootPaths = listOf(
482
+ "/system/app/Superuser.apk",
483
+ "/system/bin/su",
484
+ "/system/xbin/su",
485
+ "/sbin/su",
486
+ "/su/bin/su",
487
+ "/data/local/su",
488
+ "/data/local/bin/su",
489
+ "/data/local/xbin/su",
490
+ "/data/adb/magisk",
491
+ "/sbin/.magisk",
492
+ "/init.magisk.rc"
493
+ )
494
+ rootPaths.firstOrNull { File(it).exists() }?.let { signals.add("root_artifact:$it") }
495
+
496
+ System.getenv("PATH")
497
+ ?.split(File.pathSeparator)
498
+ ?.firstOrNull { directory -> File(directory, "su").exists() }
499
+ ?.let { signals.add("su_binary_on_path") }
500
+
501
+ val rootManagementPackages = listOf(
502
+ "com.topjohnwu.magisk",
503
+ "eu.chainfire.supersu",
504
+ "com.noshufou.android.su",
505
+ "com.koushikdutta.superuser",
506
+ "com.thirdparty.superuser"
507
+ )
508
+ rootManagementPackages.firstOrNull { packageName ->
509
+ try {
510
+ @Suppress("DEPRECATION")
511
+ context.packageManager.getApplicationInfo(packageName, 0)
512
+ true
513
+ } catch (_: Exception) {
514
+ false
515
+ }
516
+ }?.let { signals.add("root_management_app:$it") }
517
+
518
+ return signals.toList()
519
+ }
520
+
521
+ private fun getDeviceSecurityStatus(): Map<String, Any> {
522
+ val context = appContext.reactContext
523
+ ?: return mapOf(
524
+ "platform" to "android",
525
+ "supported" to false,
526
+ "isRooted" to false,
527
+ "isJailbroken" to false,
528
+ "isEmulator" to false,
529
+ "isDeveloperModeEnabled" to false,
530
+ "developerModeCheckSupported" to true,
531
+ "isDebuggerAttached" to false,
532
+ "isCompromised" to false,
533
+ "riskSignals" to listOf("android_context_unavailable")
534
+ )
535
+
536
+ val signals = rootSignals(context).toMutableSet()
537
+ val isEmulator = isProbablyEmulator()
538
+ val developerOptionsEnabled = try {
539
+ Settings.Global.getInt(
540
+ context.contentResolver,
541
+ Settings.Global.DEVELOPMENT_SETTINGS_ENABLED,
542
+ 0
543
+ ) == 1
544
+ } catch (_: Exception) {
545
+ false
546
+ }
547
+ val adbEnabled = try {
548
+ Settings.Global.getInt(context.contentResolver, Settings.Global.ADB_ENABLED, 0) == 1
549
+ } catch (_: Exception) {
550
+ false
551
+ }
552
+ val developerModeEnabled = developerOptionsEnabled || adbEnabled
553
+ val debuggerAttached = Debug.isDebuggerConnected() || Debug.waitingForDebugger()
554
+
555
+ if (isEmulator) signals.add("android_emulator_build")
556
+ if (developerOptionsEnabled) signals.add("android_developer_options_enabled")
557
+ if (adbEnabled) signals.add("android_adb_enabled")
558
+ if (debuggerAttached) signals.add("android_debugger_attached")
559
+
560
+ val isRooted = signals.any {
561
+ it.startsWith("root_") || it.startsWith("su_") || it == "android_build_test_keys"
562
+ }
563
+
564
+ return mapOf(
565
+ "platform" to "android",
566
+ "supported" to true,
567
+ "isRooted" to isRooted,
568
+ "isJailbroken" to false,
569
+ "isEmulator" to isEmulator,
570
+ "isDeveloperModeEnabled" to developerModeEnabled,
571
+ "developerModeCheckSupported" to true,
572
+ "isDebuggerAttached" to debuggerAttached,
573
+ "isCompromised" to isRooted,
574
+ "riskSignals" to signals.toList()
575
+ )
576
+ }
577
+
443
578
  override fun definition() = ModuleDefinition {
444
579
  Name("SanctumKeySdk")
445
580
 
@@ -457,6 +592,18 @@ class SanctumKeySdkModule : Module() {
457
592
  }
458
593
  }
459
594
 
595
+ AsyncFunction("checkDeviceSecurity") { promise: Promise ->
596
+ try {
597
+ promise.resolve(getDeviceSecurityStatus())
598
+ } catch (error: Exception) {
599
+ promise.reject(
600
+ "DEVICE_SECURITY_CHECK_ERROR",
601
+ error.message ?: "Unable to check device security",
602
+ error
603
+ )
604
+ }
605
+ }
606
+
460
607
  AsyncFunction("requestCameraPermission") { promise: Promise ->
461
608
  val activity = appContext.currentActivity
462
609
  if (activity == null) {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.60",
3
+ "version": "1.0.62",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",
@@ -1,8 +1,10 @@
1
1
  import { NativeModule } from 'expo';
2
2
  import { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';
3
+ import type { DeviceSecurityCheckResult } from './security/deviceSecurity';
3
4
  declare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {
4
5
  PI: number;
5
6
  testModule(): Promise<string>;
7
+ checkDeviceSecurity(): Promise<Omit<DeviceSecurityCheckResult, 'checkedAt'>>;
6
8
  requestCameraPermission(): Promise<boolean>;
7
9
  decodeBarcodeFromImage?(imagePath: string, formats: string[]): Promise<string | null>;
8
10
  openCameraWithInstructions(instructions: string): Promise<{
@@ -1 +1 @@
1
- {"version":3,"file":"SanctumKeySdkModule.d.ts","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAElE,OAAO,OAAO,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAC7B,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAC3C,sBAAsB,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IACrF,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9G,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnH;;AAGD,wBAAyE"}
1
+ {"version":3,"file":"SanctumKeySdkModule.d.ts","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAE3E,OAAO,OAAO,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IAC/E,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC;IAC7B,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAC;IAC5E,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAC3C,sBAAsB,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IACrF,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9G,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACnH;;AAGD,wBAAyE"}
@@ -1 +1 @@
1
- {"version":3,"file":"SanctumKeySdkModule.js","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAexD,yDAAyD;AACzD,eAAe,mBAAmB,CAAsB,eAAe,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nimport { NativeModule } from 'expo';\n\nimport { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';\n\ndeclare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {\n PI: number;\n testModule(): Promise<string>;\n requestCameraPermission(): Promise<boolean>;\n decodeBarcodeFromImage?(imagePath: string, formats: string[]): Promise<string | null>;\n openCameraWithInstructions(instructions: string): Promise<{ success: boolean; path?: string; error?: string }>;\n openFilePicker(allowedTypes: string[]): Promise<{ success: boolean; uri?: string; path?: string; error?: string }>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<SanctumKeySdkModule>('SanctumKeySdk');\n"]}
1
+ {"version":3,"file":"SanctumKeySdkModule.js","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAiBxD,yDAAyD;AACzD,eAAe,mBAAmB,CAAsB,eAAe,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\n\nimport { NativeModule } from 'expo';\n\nimport { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';\nimport type { DeviceSecurityCheckResult } from './security/deviceSecurity';\n\ndeclare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {\n PI: number;\n testModule(): Promise<string>;\n checkDeviceSecurity(): Promise<Omit<DeviceSecurityCheckResult, 'checkedAt'>>;\n requestCameraPermission(): Promise<boolean>;\n decodeBarcodeFromImage?(imagePath: string, formats: string[]): Promise<string | null>;\n openCameraWithInstructions(instructions: string): Promise<{ success: boolean; path?: string; error?: string }>;\n openFilePicker(allowedTypes: string[]): Promise<{ success: boolean; uri?: string; path?: string; error?: string }>;\n}\n\n// This call loads the native module object from the JSI.\nexport default requireNativeModule<SanctumKeySdkModule>('SanctumKeySdk');\n"]}
@@ -1,7 +1,9 @@
1
1
  import { NativeModule } from 'expo';
2
2
  import { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';
3
+ import type { DeviceSecurityCheckResult } from './security/deviceSecurity';
3
4
  declare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {
4
5
  PI: number;
6
+ checkDeviceSecurity(): Promise<Omit<DeviceSecurityCheckResult, 'checkedAt'>>;
5
7
  requestCameraPermission(): Promise<boolean>;
6
8
  openCameraWithInstructions(instructions: string): Promise<{
7
9
  success: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"SanctumKeySdkModule.web.d.ts","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAElE,cAAM,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IACvE,EAAE,SAAW;IAEP,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK3C,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ9G,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAOzH;;AAED,wBAA6E"}
1
+ {"version":3,"file":"SanctumKeySdkModule.web.d.ts","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.web.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEpC,OAAO,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAE3E,cAAM,mBAAoB,SAAQ,YAAY,CAAC,yBAAyB,CAAC;IACvE,EAAE,SAAW;IAEP,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,yBAAyB,EAAE,WAAW,CAAC,CAAC;IAe5E,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK3C,0BAA0B,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAQ9G,cAAc,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAOzH;;AAED,wBAA6E"}
@@ -2,6 +2,20 @@ import { registerWebModule } from 'expo-modules-core';
2
2
  import { NativeModule } from 'expo';
3
3
  class SanctumKeySdkModule extends NativeModule {
4
4
  PI = Math.PI;
5
+ async checkDeviceSecurity() {
6
+ return {
7
+ platform: 'web',
8
+ supported: false,
9
+ isRooted: false,
10
+ isJailbroken: false,
11
+ isEmulator: false,
12
+ isDeveloperModeEnabled: false,
13
+ developerModeCheckSupported: false,
14
+ isDebuggerAttached: false,
15
+ isCompromised: false,
16
+ riskSignals: ['native_device_security_check_unavailable_on_web'],
17
+ };
18
+ }
5
19
  async requestCameraPermission() {
6
20
  console.warn('Camera permission not available on web');
7
21
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"SanctumKeySdkModule.web.js","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAIpC,MAAM,mBAAoB,SAAQ,YAAuC;IACvE,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;IAEb,KAAK,CAAC,uBAAuB;QAC3B,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,0BAA0B,CAAC,YAAoB;QACnD,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,sCAAsC;SAC9C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,YAAsB;QACzC,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACjD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,2CAA2C;SACnD,CAAC;IACJ,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { registerWebModule } from 'expo-modules-core';\nimport { NativeModule } from 'expo';\n\nimport { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';\n\nclass SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {\n PI = Math.PI;\n \n async requestCameraPermission(): Promise<boolean> {\n console.warn('Camera permission not available on web');\n return false;\n }\n \n async openCameraWithInstructions(instructions: string): Promise<{ success: boolean; path?: string; error?: string }> {\n console.warn('Camera not available on web');\n return {\n success: false,\n error: 'Camera not available on web platform'\n };\n }\n \n async openFilePicker(allowedTypes: string[]): Promise<{ success: boolean; uri?: string; path?: string; error?: string }> {\n console.warn('File picker not available on web');\n return {\n success: false,\n error: 'File picker not available on web platform'\n };\n }\n}\n\nexport default registerWebModule(SanctumKeySdkModule, 'SanctumKeySdkModule');\n"]}
1
+ {"version":3,"file":"SanctumKeySdkModule.web.js","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAKpC,MAAM,mBAAoB,SAAQ,YAAuC;IACvE,EAAE,GAAG,IAAI,CAAC,EAAE,CAAC;IAEb,KAAK,CAAC,mBAAmB;QACvB,OAAO;YACL,QAAQ,EAAE,KAAK;YACf,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE,KAAK;YACnB,UAAU,EAAE,KAAK;YACjB,sBAAsB,EAAE,KAAK;YAC7B,2BAA2B,EAAE,KAAK;YAClC,kBAAkB,EAAE,KAAK;YACzB,aAAa,EAAE,KAAK;YACpB,WAAW,EAAE,CAAC,iDAAiD,CAAC;SACjE,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,uBAAuB;QAC3B,OAAO,CAAC,IAAI,CAAC,wCAAwC,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,0BAA0B,CAAC,YAAoB;QACnD,OAAO,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QAC5C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,sCAAsC;SAC9C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,YAAsB;QACzC,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACjD,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,2CAA2C;SACnD,CAAC;IACJ,CAAC;CACF;AAED,eAAe,iBAAiB,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,CAAC","sourcesContent":["import { registerWebModule } from 'expo-modules-core';\nimport { NativeModule } from 'expo';\n\nimport { SanctumKeySdkModuleEvents } from './SanctumKeySdk.types';\nimport type { DeviceSecurityCheckResult } from './security/deviceSecurity';\n\nclass SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents> {\n PI = Math.PI;\n\n async checkDeviceSecurity(): Promise<Omit<DeviceSecurityCheckResult, 'checkedAt'>> {\n return {\n platform: 'web',\n supported: false,\n isRooted: false,\n isJailbroken: false,\n isEmulator: false,\n isDeveloperModeEnabled: false,\n developerModeCheckSupported: false,\n isDebuggerAttached: false,\n isCompromised: false,\n riskSignals: ['native_device_security_check_unavailable_on_web'],\n };\n }\n \n async requestCameraPermission(): Promise<boolean> {\n console.warn('Camera permission not available on web');\n return false;\n }\n \n async openCameraWithInstructions(instructions: string): Promise<{ success: boolean; path?: string; error?: string }> {\n console.warn('Camera not available on web');\n return {\n success: false,\n error: 'Camera not available on web platform'\n };\n }\n \n async openFilePicker(allowedTypes: string[]): Promise<{ success: boolean; uri?: string; path?: string; error?: string }> {\n console.warn('File picker not available on web');\n return {\n success: false,\n error: 'File picker not available on web platform'\n };\n }\n}\n\nexport default registerWebModule(SanctumKeySdkModule, 'SanctumKeySdkModule');\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"CountrySelection.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAoB,MAAM,uBAAuB,CAAC;AAIrE,UAAU,qBAAqB;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAoH5D,CAAC"}
1
+ {"version":3,"file":"CountrySelection.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,UAAU,EAAoB,MAAM,uBAAuB,CAAC;AAIrE,UAAU,qBAAqB;IAC7B,OAAO,EAAE,UAAU,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAsH5D,CAAC"}
@@ -60,7 +60,7 @@ export const CountrySelection = ({ element, value, onValueChange, error, }) => {
60
60
 
61
61
  <FlatList data={filteredCountries} keyExtractor={(item) => item.value} renderItem={renderCountry} showsVerticalScrollIndicator={true} // Enable scroll indicator for better UX visibility
62
62
  contentContainerStyle={styles.listContent} keyboardShouldPersistTaps="handled" keyboardDismissMode="on-drag" // 🚨 UX FIX: Hides keyboard naturally when user starts scrolling
63
- initialNumToRender={15} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={true} accessibilityRole="list" ListEmptyComponent={<Text style={styles.emptyText} accessibilityRole="text">
63
+ nestedScrollEnabled persistentScrollbar initialNumToRender={15} maxToRenderPerBatch={20} windowSize={5} removeClippedSubviews={true} accessibilityRole="list" ListEmptyComponent={<Text style={styles.emptyText} accessibilityRole="text">
64
64
  Aucun pays configuré ou trouvé.
65
65
  </Text>}/>
66
66
  {error && (<Text style={styles.errorText} accessibilityRole="alert">{error}</Text>)}
@@ -1 +1 @@
1
- {"version":3,"file":"CountrySelection.js","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelection.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState, useMemo, useEffect } from 'react';\nimport { View, Text, TouchableOpacity, StyleSheet, FlatList, TextInput } from 'react-native';\nimport { KYCElement, KYCElementOption } from '../../types/KYC.types';\nimport { sdkLogger } from '../../utils/logger'; \nimport { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow'; \n\ninterface CountrySelectionProps {\n element: KYCElement;\n value?: string;\n onValueChange: (value: string) => void;\n error?: string;\n}\n\nexport const CountrySelection: React.FC<CountrySelectionProps> = ({\n element,\n value,\n onValueChange,\n error,\n}) => {\n const countries = element.options || [];\n const [searchQuery, setSearchQuery] = useState('');\n \n const { state } = useTemplateKYCFlowContext();\n\n useEffect(() => {\n if (state.session?.session_id) {\n sdkLogger.info(\"User started country selection step\", { \n availableCountriesCount: countries.length \n });\n }\n }, [state.session?.session_id, countries.length]);\n\n // Filter countries based on user input\n const filteredCountries = useMemo(() => {\n if (!searchQuery.trim()) return countries;\n return countries.filter(country =>\n country.label.toLowerCase().includes(searchQuery.toLowerCase())\n );\n }, [countries, searchQuery]);\n\n // Extract the render logic into a separate function for FlatList performance\n const renderCountry = ({ item: country }: { item: KYCElementOption }) => {\n const isSelected = value === country.value;\n\n return (\n <TouchableOpacity\n style={[\n styles.option,\n isSelected && styles.selectedOption\n ]}\n onPress={() => {\n sdkLogger.info(\"User selected country\", { countryCode: country.value }); // 🚨 LOG\n onValueChange(country.value);\n }}\n activeOpacity={0.7}\n // Accessibility Enhancements\n accessible={true}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: isSelected }}\n accessibilityLabel={`${country.label}. ${isSelected ? 'Selected' : 'Unselected'}.`}\n accessibilityHint={`Tap to select ${country.label} as your country`}\n >\n <Text style={styles.optionIcon}>{country.icon}</Text>\n <View style={styles.optionContent}>\n <Text style={[\n styles.optionLabel,\n isSelected && styles.selectedOptionLabel\n ]}>\n {country.label}\n </Text>\n {country.description && (\n <Text style={styles.optionDescription}>{country.description}</Text>\n )}\n </View>\n {isSelected && (\n <View style={styles.checkmark}>\n <Text style={styles.checkmarkText}>✓</Text>\n </View>\n )}\n </TouchableOpacity>\n );\n };\n\n return (\n <View style={styles.container}>\n <View style={styles.headerContainer}>\n <Text style={styles.title} accessibilityRole=\"header\">{element.title}</Text>\n <Text style={styles.description}>{element.description}</Text>\n\n {/* Search Bar */}\n <TextInput\n style={styles.searchInput}\n placeholder=\"Rechercher un pays...\"\n placeholderTextColor=\"#999\"\n value={searchQuery}\n onChangeText={setSearchQuery}\n clearButtonMode=\"while-editing\" // Adds a clear 'x' on iOS\n \n // Accessibility for Search\n accessibilityRole=\"search\"\n accessibilityLabel=\"Search countries\"\n accessibilityHint=\"Type to filter the list of configured countries below\"\n />\n </View>\n\n <FlatList\n data={filteredCountries}\n keyExtractor={(item) => item.value}\n renderItem={renderCountry}\n showsVerticalScrollIndicator={true} // Enable scroll indicator for better UX visibility\n contentContainerStyle={styles.listContent}\n keyboardShouldPersistTaps=\"handled\"\n keyboardDismissMode=\"on-drag\" // 🚨 UX FIX: Hides keyboard naturally when user starts scrolling\n initialNumToRender={15}\n maxToRenderPerBatch={20}\n windowSize={5}\n removeClippedSubviews={true}\n accessibilityRole=\"list\"\n ListEmptyComponent={\n <Text style={styles.emptyText} accessibilityRole=\"text\">\n Aucun pays configuré ou trouvé.\n </Text>\n }\n />\n {error && (\n <Text style={styles.errorText} accessibilityRole=\"alert\">{error}</Text>\n )}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#fff', },\n headerContainer: { paddingHorizontal: 16, paddingTop: 16, paddingBottom: 8, },\n title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center', },\n description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 16, lineHeight: 22, },\n searchInput: { backgroundColor: '#f5f5f5', borderRadius: 10, padding: 12, fontSize: 16, color: '#333', borderWidth: 1, borderColor: '#e5e5e5', marginBottom: 8, },\n listContent: { paddingHorizontal: 16, paddingBottom: 24, },\n emptyText: { textAlign: 'center', color: '#999', marginTop: 20, fontSize: 16, },\n option: { flexDirection: 'row', alignItems: 'center', padding: 16, backgroundColor: 'white', borderRadius: 12, marginBottom: 12, borderWidth: 2, borderColor: '#e5e5e5', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 3.84, elevation: 3, },\n selectedOption: { borderColor: '#4CAF50', backgroundColor: '#f0f9f0', },\n optionIcon: { fontSize: 32, marginRight: 16, },\n optionContent: { flex: 1, },\n optionLabel: { fontSize: 18, fontWeight: '600', color: '#333', },\n selectedOptionLabel: { color: '#4CAF50', },\n optionDescription: { fontSize: 14, color: '#666', marginTop: 4, },\n checkmark: { width: 24, height: 24, borderRadius: 12, backgroundColor: '#4CAF50', justifyContent: 'center', alignItems: 'center', },\n checkmarkText: { color: 'white', fontSize: 16, fontWeight: 'bold', },\n errorText: { color: '#dc2626', fontSize: 14, margin: 16, textAlign: 'center', },\n});"]}
1
+ {"version":3,"file":"CountrySelection.js","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelection.tsx"],"names":[],"mappings":"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","sourcesContent":["import React, { useState, useMemo, useEffect } from 'react';\nimport { View, Text, TouchableOpacity, StyleSheet, FlatList, TextInput } from 'react-native';\nimport { KYCElement, KYCElementOption } from '../../types/KYC.types';\nimport { sdkLogger } from '../../utils/logger'; \nimport { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow'; \n\ninterface CountrySelectionProps {\n element: KYCElement;\n value?: string;\n onValueChange: (value: string) => void;\n error?: string;\n}\n\nexport const CountrySelection: React.FC<CountrySelectionProps> = ({\n element,\n value,\n onValueChange,\n error,\n}) => {\n const countries = element.options || [];\n const [searchQuery, setSearchQuery] = useState('');\n \n const { state } = useTemplateKYCFlowContext();\n\n useEffect(() => {\n if (state.session?.session_id) {\n sdkLogger.info(\"User started country selection step\", { \n availableCountriesCount: countries.length \n });\n }\n }, [state.session?.session_id, countries.length]);\n\n // Filter countries based on user input\n const filteredCountries = useMemo(() => {\n if (!searchQuery.trim()) return countries;\n return countries.filter(country =>\n country.label.toLowerCase().includes(searchQuery.toLowerCase())\n );\n }, [countries, searchQuery]);\n\n // Extract the render logic into a separate function for FlatList performance\n const renderCountry = ({ item: country }: { item: KYCElementOption }) => {\n const isSelected = value === country.value;\n\n return (\n <TouchableOpacity\n style={[\n styles.option,\n isSelected && styles.selectedOption\n ]}\n onPress={() => {\n sdkLogger.info(\"User selected country\", { countryCode: country.value }); // 🚨 LOG\n onValueChange(country.value);\n }}\n activeOpacity={0.7}\n // Accessibility Enhancements\n accessible={true}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: isSelected }}\n accessibilityLabel={`${country.label}. ${isSelected ? 'Selected' : 'Unselected'}.`}\n accessibilityHint={`Tap to select ${country.label} as your country`}\n >\n <Text style={styles.optionIcon}>{country.icon}</Text>\n <View style={styles.optionContent}>\n <Text style={[\n styles.optionLabel,\n isSelected && styles.selectedOptionLabel\n ]}>\n {country.label}\n </Text>\n {country.description && (\n <Text style={styles.optionDescription}>{country.description}</Text>\n )}\n </View>\n {isSelected && (\n <View style={styles.checkmark}>\n <Text style={styles.checkmarkText}>✓</Text>\n </View>\n )}\n </TouchableOpacity>\n );\n };\n\n return (\n <View style={styles.container}>\n <View style={styles.headerContainer}>\n <Text style={styles.title} accessibilityRole=\"header\">{element.title}</Text>\n <Text style={styles.description}>{element.description}</Text>\n\n {/* Search Bar */}\n <TextInput\n style={styles.searchInput}\n placeholder=\"Rechercher un pays...\"\n placeholderTextColor=\"#999\"\n value={searchQuery}\n onChangeText={setSearchQuery}\n clearButtonMode=\"while-editing\" // Adds a clear 'x' on iOS\n \n // Accessibility for Search\n accessibilityRole=\"search\"\n accessibilityLabel=\"Search countries\"\n accessibilityHint=\"Type to filter the list of configured countries below\"\n />\n </View>\n\n <FlatList\n data={filteredCountries}\n keyExtractor={(item) => item.value}\n renderItem={renderCountry}\n showsVerticalScrollIndicator={true} // Enable scroll indicator for better UX visibility\n contentContainerStyle={styles.listContent}\n keyboardShouldPersistTaps=\"handled\"\n keyboardDismissMode=\"on-drag\" // 🚨 UX FIX: Hides keyboard naturally when user starts scrolling\n nestedScrollEnabled\n persistentScrollbar\n initialNumToRender={15}\n maxToRenderPerBatch={20}\n windowSize={5}\n removeClippedSubviews={true}\n accessibilityRole=\"list\"\n ListEmptyComponent={\n <Text style={styles.emptyText} accessibilityRole=\"text\">\n Aucun pays configuré ou trouvé.\n </Text>\n }\n />\n {error && (\n <Text style={styles.errorText} accessibilityRole=\"alert\">{error}</Text>\n )}\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#fff', },\n headerContainer: { paddingHorizontal: 16, paddingTop: 16, paddingBottom: 8, },\n title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center', },\n description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 16, lineHeight: 22, },\n searchInput: { backgroundColor: '#f5f5f5', borderRadius: 10, padding: 12, fontSize: 16, color: '#333', borderWidth: 1, borderColor: '#e5e5e5', marginBottom: 8, },\n listContent: { paddingHorizontal: 16, paddingBottom: 24, },\n emptyText: { textAlign: 'center', color: '#999', marginTop: 20, fontSize: 16, },\n option: { flexDirection: 'row', alignItems: 'center', padding: 16, backgroundColor: 'white', borderRadius: 12, marginBottom: 12, borderWidth: 2, borderColor: '#e5e5e5', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.05, shadowRadius: 3.84, elevation: 3, },\n selectedOption: { borderColor: '#4CAF50', backgroundColor: '#f0f9f0', },\n optionIcon: { fontSize: 32, marginRight: 16, },\n optionContent: { flex: 1, },\n optionLabel: { fontSize: 18, fontWeight: '600', color: '#333', },\n selectedOptionLabel: { color: '#4CAF50', },\n optionDescription: { fontSize: 14, color: '#666', marginTop: 4, },\n checkmark: { width: 24, height: 24, borderRadius: 12, backgroundColor: '#4CAF50', justifyContent: 'center', alignItems: 'center', },\n checkmarkText: { color: 'white', fontSize: 16, fontWeight: 'bold', },\n errorText: { color: '#dc2626', fontSize: 14, margin: 16, textAlign: 'center', },\n});\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"CountrySelectionTemplate.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelectionTemplate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAyC,OAAO,EAAgB,MAAM,uBAAuB,CAAC;AAQxH,UAAU,6BAA6B;IACrC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAqT5E,CAAC"}
1
+ {"version":3,"file":"CountrySelectionTemplate.d.ts","sourceRoot":"","sources":["../../../../src/components/KYCElements/CountrySelectionTemplate.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAuC,MAAM,OAAO,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAyC,OAAO,EAAgB,MAAM,uBAAuB,CAAC;AAQxH,UAAU,6BAA6B;IACrC,SAAS,EAAE,iBAAiB,CAAC;IAC7B,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,aAAa,EAAE,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAeD,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAoW5E,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useMemo, useEffect } from 'react';
2
- import { View, Text, TouchableOpacity, StyleSheet, ScrollView, TextInput } from 'react-native';
2
+ import { FlatList, Platform, ScrollView, StyleSheet, Text, TextInput, TouchableOpacity, View } from 'react-native';
3
3
  import { countryMapping } from '../../config/region_mapping';
4
4
  import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
5
5
  import { Button } from '../ui/Button';
@@ -195,18 +195,26 @@ export const CountrySelectionTemplate = ({ component, value, onValueChange, erro
195
195
  };
196
196
  const renderCountrySelection = () => {
197
197
  return (<>
198
- <TextInput style={styles.searchBar} placeholder={state.currentLanguage === "en" ? "🔍 Search country..." : "🔍 Rechercher un pays..."} placeholderTextColor="#94A3B8" value={searchQuery} onChangeText={setSearchQuery} autoCorrect={false}/>
199
- <ScrollView style={styles.listContainer} showsVerticalScrollIndicator={false}>
200
- {filteredCountries.map((country) => (<TouchableOpacity key={country.code} style={[styles.option, selectedCountryCode === country.code && styles.selectedOption]} onPress={() => handleCountrySelect(country.code)}>
201
- <Text style={styles.flag}>{country.flag}</Text>
202
- <View style={styles.optionContent}>
203
- <Text style={[styles.optionName, selectedCountryCode === country.code && styles.selectedOptionName]}>{state.currentLanguage === "en" ? country.name_en : country.name}</Text>
204
- <Text style={styles.optionCode}>{country.code}</Text>
205
- </View>
206
- {selectedCountryCode === country.code && (<View style={[styles.checkmark, { backgroundColor: component.ui.themeColor || '#2DBD60' }]}><Text style={styles.checkmarkText}>✓</Text></View>)}
207
- </TouchableOpacity>))}
208
- {filteredCountries.length === 0 && (<Text style={styles.emptyText}>{state.currentLanguage === "en" ? "No matching countries found." : "Aucun pays trouvé."}</Text>)}
209
- </ScrollView>
198
+ <TextInput style={styles.searchBar} placeholder={state.currentLanguage === "en" ? "Search countries" : "Rechercher un pays"} placeholderTextColor="#64748B" value={searchQuery} onChangeText={setSearchQuery} autoCorrect={false} autoCapitalize="none" clearButtonMode="while-editing" returnKeyType="search" accessibilityRole="search" accessibilityLabel={state.currentLanguage === "en" ? "Search countries" : "Rechercher un pays"} accessibilityHint={state.currentLanguage === "en" ? "Filters the country list as you type" : "Filtre la liste des pays pendant la saisie"}/>
199
+ <FlatList style={styles.listContainer} contentContainerStyle={[
200
+ styles.listContent,
201
+ filteredCountries.length === 0 && styles.emptyListContent,
202
+ ]} data={filteredCountries} keyExtractor={(country) => country.code} renderItem={({ item: country }) => {
203
+ const isSelected = selectedCountryCode === country.code;
204
+ const countryName = state.currentLanguage === "en" ? country.name_en : country.name;
205
+ return (<TouchableOpacity style={[styles.option, isSelected && styles.selectedOption]} onPress={() => handleCountrySelect(country.code)} activeOpacity={0.7} accessible accessibilityRole="radio" accessibilityState={{ selected: isSelected }} accessibilityLabel={`${countryName}, ${country.code}`} accessibilityHint={state.currentLanguage === "en" ? "Select this issuing country" : "Sélectionner ce pays émetteur"}>
206
+ <Text style={styles.flag} accessible={false}>{country.flag}</Text>
207
+ <View style={styles.optionContent}>
208
+ <Text style={[styles.optionName, isSelected && styles.selectedOptionName]}>{countryName}</Text>
209
+ <Text style={styles.optionCode}>{country.code}</Text>
210
+ </View>
211
+ {isSelected && (<View style={[styles.checkmark, { backgroundColor: component.ui.themeColor || '#2DBD60' }]} accessible={false}>
212
+ <Text style={styles.checkmarkText}>✓</Text>
213
+ </View>)}
214
+ </TouchableOpacity>);
215
+ }} ListEmptyComponent={<Text style={styles.emptyText} accessibilityRole="text">
216
+ {state.currentLanguage === "en" ? "No matching countries found." : "Aucun pays trouvé."}
217
+ </Text>} showsVerticalScrollIndicator persistentScrollbar={Platform.OS === 'android'} keyboardShouldPersistTaps="handled" keyboardDismissMode={Platform.OS === 'ios' ? 'interactive' : 'on-drag'} nestedScrollEnabled initialNumToRender={12} maxToRenderPerBatch={12} windowSize={7} accessibilityRole="list"/>
210
218
  </>);
211
219
  };
212
220
  const renderDocumentTypeSelection = () => {
@@ -303,14 +311,16 @@ export const CountrySelectionTemplate = ({ component, value, onValueChange, erro
303
311
  </View>);
304
312
  };
305
313
  const styles = StyleSheet.create({
306
- root: { flex: 1, maxWidth: 760, width: '100%' },
307
- container: { backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.35, shadowRadius: 4.84, elevation: 10 },
314
+ root: { flex: 1, minHeight: 0, maxWidth: 760, width: '100%' },
315
+ container: { flex: 1, minHeight: 0, backgroundColor: 'white', margin: 10, borderRadius: 10, paddingVertical: 16, paddingHorizontal: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.35, shadowRadius: 4.84, elevation: 10 },
308
316
  progressContainer: { marginBottom: 16, alignItems: 'center' },
309
317
  progressText: { fontSize: 14, color: '#666', fontWeight: '500' },
310
318
  title: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 8, textAlign: 'center' },
311
319
  description: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 24, lineHeight: 22 },
312
320
  searchBar: { height: 52, backgroundColor: '#F1F5F9', borderRadius: 12, paddingHorizontal: 16, fontSize: 16, color: '#1E293B', marginBottom: 16, borderWidth: 1, borderColor: '#CBD5E1', fontWeight: '500' },
313
- listContainer: { maxHeight: 360, marginBottom: 24 },
321
+ listContainer: { flex: 1, minHeight: 120, marginBottom: 12 },
322
+ listContent: { paddingBottom: 8 },
323
+ emptyListContent: { flexGrow: 1, justifyContent: 'center' },
314
324
  option: { flexDirection: 'row', alignItems: 'center', padding: 16, backgroundColor: '#F8F9FA', borderRadius: 12, marginBottom: 12, borderWidth: 2, borderColor: '#E9ECEF', minHeight: 64 },
315
325
  selectedOption: { borderColor: '#2DBD60', backgroundColor: '#F0F9F0' },
316
326
  flag: { fontSize: 32, marginRight: 16 },
@@ -322,7 +332,7 @@ const styles = StyleSheet.create({
322
332
  checkmarkText: { color: 'white', fontSize: 16, fontWeight: 'bold' },
323
333
  infoBox: { backgroundColor: '#F9FAFB', padding: 12, borderRadius: 12, marginBottom: 16 },
324
334
  infoText: { fontSize: 14, color: '#666', lineHeight: 20 },
325
- buttonContainer: { flexDirection: 'row', marginTop: 24, gap: 12 },
335
+ buttonContainer: { flexDirection: 'row', marginTop: 12, gap: 12 },
326
336
  errorText: { color: '#dc2626', fontSize: 14, marginTop: 8, textAlign: 'center' },
327
337
  emptyText: { textAlign: 'center', color: '#64748B', fontSize: 16, marginTop: 24, fontWeight: '500' }
328
338
  });