@sanctum-key/react-native-sdk 1.0.53 → 1.0.56

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 (128) hide show
  1. package/README.md +7 -2
  2. package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/results.bin +1 -0
  3. package/android/build/.transforms/3db9b15041d4285a820fc84462995ea1/transformed/classes/classes_dex/classes.dex +0 -0
  4. package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
  5. package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
  6. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
  7. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
  8. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream.len +0 -0
  11. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.len +0 -0
  12. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
  13. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
  14. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
  15. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
  16. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream.len +0 -0
  17. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.len +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.keystream.len +0 -0
  29. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.len +0 -0
  30. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
  31. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
  32. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
  33. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
  34. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
  35. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
  36. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
  37. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
  38. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
  39. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
  40. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
  41. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
  42. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
  43. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
  44. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
  45. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
  46. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
  47. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
  48. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
  49. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
  50. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
  51. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
  52. package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
  53. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  54. package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
  55. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  56. package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
  57. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$BarcodeRegion.class +0 -0
  58. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$Companion.class +0 -0
  59. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$1.class → SanctumKeySdkModule$definition$1$6$1.class} +0 -0
  60. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$5$2.class → SanctumKeySdkModule$definition$1$6$2.class} +0 -0
  61. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$1.class} +0 -0
  62. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$5.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$2.class} +0 -0
  63. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$3.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$3.class} +0 -0
  64. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$4.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$4.class} +0 -0
  65. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$2.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$5.class} +0 -0
  66. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunction$6.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$6.class} +0 -0
  67. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$1.class} +0 -0
  68. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$3.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$2.class} +0 -0
  69. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$3.class +0 -0
  70. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$4.class +0 -0
  71. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$2.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$5.class} +0 -0
  72. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$6.class +0 -0
  73. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$AsyncFunctionWithPromise$4.class → SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$7.class} +0 -0
  74. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$$inlined$View$1.class → SanctumKeySdkModule$definition$lambda$16$$inlined$View$1.class} +0 -0
  75. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$1.class → SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$1.class} +0 -0
  76. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$5$lambda$4$$inlined$Prop$2.class → SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$2.class} +0 -0
  77. package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule.class +0 -0
  78. package/android/build.gradle +7 -0
  79. package/android/src/main/java/kyc/sanctumkey/com/SanctumKeySdkModule.kt +428 -1
  80. package/build/package.json +3 -1
  81. package/build/src/SanctumKeySdkModule.d.ts +1 -0
  82. package/build/src/SanctumKeySdkModule.d.ts.map +1 -1
  83. package/build/src/SanctumKeySdkModule.js.map +1 -1
  84. package/build/src/components/EnhancedCameraView.d.ts.map +1 -1
  85. package/build/src/components/EnhancedCameraView.js +50 -41
  86. package/build/src/components/EnhancedCameraView.js.map +1 -1
  87. package/build/src/components/EnhancedCameraView.web.d.ts +2 -0
  88. package/build/src/components/EnhancedCameraView.web.d.ts.map +1 -1
  89. package/build/src/components/EnhancedCameraView.web.js +11 -6
  90. package/build/src/components/EnhancedCameraView.web.js.map +1 -1
  91. package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  92. package/build/src/components/KYCElements/IDCardCapture.js +499 -78
  93. package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
  94. package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
  95. package/build/src/components/TemplateKYCExample.js +3 -9
  96. package/build/src/components/TemplateKYCExample.js.map +1 -1
  97. package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  98. package/build/src/components/TemplateKYCFlowRefactored.js +0 -2
  99. package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
  100. package/build/src/components/WebBarcodeScanner.d.ts +2 -0
  101. package/build/src/components/WebBarcodeScanner.d.ts.map +1 -1
  102. package/build/src/components/WebBarcodeScanner.js +99 -85
  103. package/build/src/components/WebBarcodeScanner.js.map +1 -1
  104. package/build/src/modules/api/KYCService.d.ts +1 -0
  105. package/build/src/modules/api/KYCService.d.ts.map +1 -1
  106. package/build/src/modules/api/KYCService.js +1 -1
  107. package/build/src/modules/api/KYCService.js.map +1 -1
  108. package/build/src/modules/api/types.d.ts +1 -0
  109. package/build/src/modules/api/types.d.ts.map +1 -1
  110. package/build/src/modules/api/types.js.map +1 -1
  111. package/build/src/web/WebKYCEntry.d.ts.map +1 -1
  112. package/build/src/web/WebKYCEntry.js +50 -32
  113. package/build/src/web/WebKYCEntry.js.map +1 -1
  114. package/package.json +3 -1
  115. package/plugin/build/withVisionCamera.d.ts +1 -0
  116. package/plugin/build/withVisionCamera.js +13 -1
  117. package/plugin/src/withVisionCamera.js +18 -2
  118. package/plugin/src/withVisionCamera.ts +19 -2
  119. package/src/SanctumKeySdkModule.ts +1 -0
  120. package/src/components/EnhancedCameraView.tsx +52 -41
  121. package/src/components/EnhancedCameraView.web.tsx +15 -5
  122. package/src/components/KYCElements/IDCardCapture.tsx +552 -75
  123. package/src/components/TemplateKYCExample.tsx +6 -14
  124. package/src/components/TemplateKYCFlowRefactored.tsx +0 -2
  125. package/src/components/WebBarcodeScanner.tsx +147 -198
  126. package/src/modules/api/KYCService.ts +3 -3
  127. package/src/modules/api/types.ts +1 -0
  128. package/src/web/WebKYCEntry.tsx +89 -50
@@ -3,7 +3,11 @@ package kyc.sanctumkey.com
3
3
  import android.Manifest
4
4
  import android.content.Context
5
5
  import android.content.pm.PackageManager
6
+ import android.graphics.Bitmap
7
+ import android.graphics.BitmapFactory
6
8
  import android.graphics.Color
9
+ import android.graphics.Matrix
10
+ import android.net.Uri
7
11
  import android.view.Gravity
8
12
  import android.view.View
9
13
  import android.widget.FrameLayout
@@ -13,18 +17,429 @@ import android.widget.LinearLayout
13
17
  import android.widget.ImageView
14
18
  import androidx.core.app.ActivityCompat
15
19
  import androidx.core.content.ContextCompat
20
+ import androidx.exifinterface.media.ExifInterface
21
+ import com.google.android.gms.tasks.Tasks
22
+ import com.google.mlkit.vision.barcode.BarcodeScanner
23
+ import com.google.mlkit.vision.barcode.BarcodeScannerOptions
24
+ import com.google.mlkit.vision.barcode.BarcodeScanning
25
+ import com.google.mlkit.vision.barcode.common.Barcode as MlKitBarcode
26
+ import com.google.mlkit.vision.common.InputImage
27
+ import com.google.zxing.BarcodeFormat
28
+ import com.google.zxing.BinaryBitmap
29
+ import com.google.zxing.DecodeHintType
30
+ import com.google.zxing.MultiFormatReader
31
+ import com.google.zxing.RGBLuminanceSource
32
+ import com.google.zxing.common.GlobalHistogramBinarizer
33
+ import com.google.zxing.common.HybridBinarizer
16
34
  import expo.modules.kotlin.AppContext
17
35
  import expo.modules.kotlin.modules.Module
18
36
  import expo.modules.kotlin.modules.ModuleDefinition
19
37
  import expo.modules.kotlin.Promise
20
38
  import expo.modules.kotlin.viewevent.EventDispatcher
21
39
  import expo.modules.kotlin.views.ExpoView
40
+ import java.io.FileInputStream
41
+ import java.io.InputStream
42
+ import java.util.EnumMap
43
+ import java.util.concurrent.TimeUnit
44
+ import kotlin.math.ceil
45
+ import kotlin.math.exp
46
+ import kotlin.math.max
47
+ import kotlin.math.min
48
+ import kotlin.math.roundToInt
22
49
 
23
50
 
24
51
  class SanctumKeySdkModule : Module() {
52
+ companion object {
53
+ // Keep the source at camera resolution until after cropping. Downsampling a
54
+ // whole ID first can reduce dense PDF417 modules to a single pixel.
55
+ private const val MAX_BARCODE_SOURCE_DIMENSION = 4096
56
+ private const val MAX_BARCODE_CANDIDATE_DIMENSION = 3000
57
+ private const val MAX_ENHANCED_PIXEL_COUNT = 4_500_000
58
+ private const val UNSHARP_SIGMA = 1.4
59
+ private const val UNSHARP_AMOUNT = 1.25
60
+ }
61
+
25
62
  private val CAMERA_PERMISSION_REQUEST = 100
26
63
  private var currentPhotoPath: String? = null
27
64
 
65
+ private data class BarcodeRegion(
66
+ val left: Double,
67
+ val top: Double,
68
+ val width: Double,
69
+ val height: Double,
70
+ val upscale: Boolean
71
+ )
72
+
73
+ private val allBarcodeFormats = listOf(
74
+ BarcodeFormat.PDF_417,
75
+ BarcodeFormat.CODE_128,
76
+ BarcodeFormat.CODE_39,
77
+ BarcodeFormat.CODE_93,
78
+ BarcodeFormat.CODABAR,
79
+ BarcodeFormat.EAN_13,
80
+ BarcodeFormat.EAN_8,
81
+ BarcodeFormat.ITF,
82
+ BarcodeFormat.UPC_A,
83
+ BarcodeFormat.UPC_E,
84
+ BarcodeFormat.QR_CODE,
85
+ BarcodeFormat.AZTEC,
86
+ BarcodeFormat.DATA_MATRIX,
87
+ BarcodeFormat.RSS_14,
88
+ BarcodeFormat.RSS_EXPANDED
89
+ )
90
+
91
+ private fun resolveBarcodeFormats(requestedFormats: Array<String>): List<BarcodeFormat> {
92
+ val resolved = requestedFormats.mapNotNull { format ->
93
+ when (format.lowercase()) {
94
+ "pdf-417", "pdf417" -> BarcodeFormat.PDF_417
95
+ "code-128", "code128" -> BarcodeFormat.CODE_128
96
+ "code-39", "code39" -> BarcodeFormat.CODE_39
97
+ "code-93", "code93" -> BarcodeFormat.CODE_93
98
+ "codabar" -> BarcodeFormat.CODABAR
99
+ "ean-13", "ean13" -> BarcodeFormat.EAN_13
100
+ "ean-8", "ean8" -> BarcodeFormat.EAN_8
101
+ "itf", "itf-14", "itf14" -> BarcodeFormat.ITF
102
+ "upc-a", "upca" -> BarcodeFormat.UPC_A
103
+ "upc-e", "upce" -> BarcodeFormat.UPC_E
104
+ "qr" -> BarcodeFormat.QR_CODE
105
+ "aztec" -> BarcodeFormat.AZTEC
106
+ "data-matrix", "datamatrix" -> BarcodeFormat.DATA_MATRIX
107
+ "gs1-data-bar", "gs1-data-bar-limited" -> BarcodeFormat.RSS_14
108
+ "gs1-data-bar-expanded" -> BarcodeFormat.RSS_EXPANDED
109
+ else -> null
110
+ }
111
+ }.distinct()
112
+
113
+ return resolved.ifEmpty { allBarcodeFormats }
114
+ }
115
+
116
+ private fun openImageStream(imagePath: String): InputStream? {
117
+ val context = appContext.reactContext ?: return null
118
+ val uri = Uri.parse(imagePath)
119
+ return when (uri.scheme?.lowercase()) {
120
+ "content", "android.resource" -> context.contentResolver.openInputStream(uri)
121
+ "file" -> uri.path?.let(::FileInputStream)
122
+ else -> FileInputStream(imagePath)
123
+ }
124
+ }
125
+
126
+ private fun loadBarcodeBitmap(imagePath: String): Bitmap? {
127
+ val bounds = BitmapFactory.Options().apply { inJustDecodeBounds = true }
128
+ openImageStream(imagePath)?.use { BitmapFactory.decodeStream(it, null, bounds) }
129
+ if (bounds.outWidth <= 0 || bounds.outHeight <= 0) return null
130
+
131
+ var sampleSize = 1
132
+ val largestDimension = max(bounds.outWidth, bounds.outHeight)
133
+ while (largestDimension / sampleSize > MAX_BARCODE_SOURCE_DIMENSION) {
134
+ sampleSize *= 2
135
+ }
136
+
137
+ val options = BitmapFactory.Options().apply {
138
+ inSampleSize = sampleSize
139
+ inPreferredConfig = Bitmap.Config.ARGB_8888
140
+ }
141
+ val decoded = openImageStream(imagePath)?.use {
142
+ BitmapFactory.decodeStream(it, null, options)
143
+ } ?: return null
144
+
145
+ val orientation = try {
146
+ openImageStream(imagePath)?.use {
147
+ ExifInterface(it).getAttributeInt(
148
+ ExifInterface.TAG_ORIENTATION,
149
+ ExifInterface.ORIENTATION_NORMAL
150
+ )
151
+ } ?: ExifInterface.ORIENTATION_NORMAL
152
+ } catch (_: Exception) {
153
+ ExifInterface.ORIENTATION_NORMAL
154
+ }
155
+
156
+ val matrix = Matrix()
157
+ when (orientation) {
158
+ ExifInterface.ORIENTATION_FLIP_HORIZONTAL -> matrix.setScale(-1f, 1f)
159
+ ExifInterface.ORIENTATION_ROTATE_180 -> matrix.setRotate(180f)
160
+ ExifInterface.ORIENTATION_FLIP_VERTICAL -> matrix.setScale(1f, -1f)
161
+ ExifInterface.ORIENTATION_TRANSPOSE -> {
162
+ matrix.setRotate(90f)
163
+ matrix.postScale(-1f, 1f)
164
+ }
165
+ ExifInterface.ORIENTATION_ROTATE_90 -> matrix.setRotate(90f)
166
+ ExifInterface.ORIENTATION_TRANSVERSE -> {
167
+ matrix.setRotate(-90f)
168
+ matrix.postScale(-1f, 1f)
169
+ }
170
+ ExifInterface.ORIENTATION_ROTATE_270 -> matrix.setRotate(-90f)
171
+ else -> return decoded
172
+ }
173
+
174
+ val oriented = Bitmap.createBitmap(decoded, 0, 0, decoded.width, decoded.height, matrix, true)
175
+ if (oriented !== decoded) decoded.recycle()
176
+ return oriented
177
+ }
178
+
179
+ private fun decodeBarcodePixels(
180
+ pixels: IntArray,
181
+ width: Int,
182
+ height: Int,
183
+ formats: List<BarcodeFormat>
184
+ ): String? {
185
+ val hints = EnumMap<DecodeHintType, Any>(DecodeHintType::class.java).apply {
186
+ put(DecodeHintType.POSSIBLE_FORMATS, formats)
187
+ put(DecodeHintType.TRY_HARDER, true)
188
+ put(DecodeHintType.CHARACTER_SET, "ISO-8859-1")
189
+ }
190
+
191
+ val normalSource = RGBLuminanceSource(width, height, pixels)
192
+ for (source in listOf(normalSource, normalSource.invert())) {
193
+ for (binarizer in listOf(HybridBinarizer(source), GlobalHistogramBinarizer(source))) {
194
+ val reader = MultiFormatReader()
195
+ try {
196
+ return reader.decode(BinaryBitmap(binarizer), hints).text
197
+ } catch (_: Exception) {
198
+ // Try the next luminance/binarization strategy.
199
+ } finally {
200
+ reader.reset()
201
+ }
202
+ }
203
+ }
204
+ return null
205
+ }
206
+
207
+ /**
208
+ * Raises local edge contrast without thresholding away one-pixel PDF417
209
+ * modules. This mirrors the unsharp pass that recovers dense Canadian IDs.
210
+ */
211
+ private fun enhanceBarcodePixels(sourcePixels: IntArray, width: Int, height: Int): IntArray {
212
+ val pixelCount = width * height
213
+ val gray = ByteArray(pixelCount)
214
+ val histogram = IntArray(256)
215
+
216
+ for (index in 0 until pixelCount) {
217
+ val color = sourcePixels[index]
218
+ val luminance = (
219
+ 299 * Color.red(color) +
220
+ 587 * Color.green(color) +
221
+ 114 * Color.blue(color) +
222
+ 500
223
+ ) / 1000
224
+ gray[index] = luminance.toByte()
225
+ histogram[luminance]++
226
+ }
227
+
228
+ val percentilePixels = max(1, (pixelCount * 0.01).roundToInt())
229
+ var low = 0
230
+ var cumulative = 0
231
+ while (low < 255 && cumulative + histogram[low] < percentilePixels) {
232
+ cumulative += histogram[low++]
233
+ }
234
+ var high = 255
235
+ cumulative = 0
236
+ while (high > low && cumulative + histogram[high] < percentilePixels) {
237
+ cumulative += histogram[high--]
238
+ }
239
+
240
+ if (high > low) {
241
+ val range = high - low
242
+ for (index in gray.indices) {
243
+ val value = gray[index].toInt() and 0xff
244
+ gray[index] = (((value - low) * 255 / range).coerceIn(0, 255)).toByte()
245
+ }
246
+ }
247
+
248
+ val radius = ceil(UNSHARP_SIGMA * 3).toInt()
249
+ val kernel = FloatArray(radius * 2 + 1)
250
+ var kernelSum = 0.0
251
+ for (offset in -radius..radius) {
252
+ val weight = exp(-(offset * offset) / (2.0 * UNSHARP_SIGMA * UNSHARP_SIGMA))
253
+ kernel[offset + radius] = weight.toFloat()
254
+ kernelSum += weight
255
+ }
256
+ for (index in kernel.indices) kernel[index] = (kernel[index] / kernelSum).toFloat()
257
+
258
+ val horizontalBlur = FloatArray(pixelCount)
259
+ for (y in 0 until height) {
260
+ val rowOffset = y * width
261
+ for (x in 0 until width) {
262
+ var blurred = 0f
263
+ for (offset in -radius..radius) {
264
+ val sampleX = (x + offset).coerceIn(0, width - 1)
265
+ blurred += (gray[rowOffset + sampleX].toInt() and 0xff) * kernel[offset + radius]
266
+ }
267
+ horizontalBlur[rowOffset + x] = blurred
268
+ }
269
+ }
270
+
271
+ val enhanced = IntArray(pixelCount)
272
+ for (y in 0 until height) {
273
+ for (x in 0 until width) {
274
+ var blurred = 0f
275
+ for (offset in -radius..radius) {
276
+ val sampleY = (y + offset).coerceIn(0, height - 1)
277
+ blurred += horizontalBlur[sampleY * width + x] * kernel[offset + radius]
278
+ }
279
+ val index = y * width + x
280
+ val original = gray[index].toInt() and 0xff
281
+ val sharpened = (original + UNSHARP_AMOUNT * (original - blurred))
282
+ .roundToInt()
283
+ .coerceIn(0, 255)
284
+ enhanced[index] = Color.rgb(sharpened, sharpened, sharpened)
285
+ }
286
+ }
287
+
288
+ return enhanced
289
+ }
290
+
291
+ private fun decodeBitmapCandidate(bitmap: Bitmap, formats: List<BarcodeFormat>): String? {
292
+ val pixels = IntArray(bitmap.width * bitmap.height)
293
+ bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
294
+
295
+ val decoded = decodeBarcodePixels(pixels, bitmap.width, bitmap.height, formats)
296
+ if (decoded != null || pixels.size > MAX_ENHANCED_PIXEL_COUNT) return decoded
297
+
298
+ return decodeBarcodePixels(
299
+ enhanceBarcodePixels(pixels, bitmap.width, bitmap.height),
300
+ bitmap.width,
301
+ bitmap.height,
302
+ formats
303
+ )
304
+ }
305
+
306
+ private fun createRegionBitmap(source: Bitmap, region: BarcodeRegion): Bitmap {
307
+ val left = (source.width * region.left).roundToInt().coerceIn(0, source.width - 1)
308
+ val top = (source.height * region.top).roundToInt().coerceIn(0, source.height - 1)
309
+ val width = (source.width * region.width).roundToInt().coerceIn(1, source.width - left)
310
+ val height = (source.height * region.height).roundToInt().coerceIn(1, source.height - top)
311
+
312
+ val isFullImage = left == 0 && top == 0 && width == source.width && height == source.height
313
+ val cropped = if (isFullImage) source else Bitmap.createBitmap(source, left, top, width, height)
314
+ val requestedScale = if (region.upscale) 2.0 else 1.0
315
+ val dimensionScale = MAX_BARCODE_CANDIDATE_DIMENSION.toDouble() / max(cropped.width, cropped.height)
316
+ val scale = min(requestedScale, dimensionScale)
317
+ if (scale in 0.95..1.05) return cropped
318
+
319
+ val scaled = Bitmap.createScaledBitmap(
320
+ cropped,
321
+ (cropped.width * scale).roundToInt(),
322
+ (cropped.height * scale).roundToInt(),
323
+ true
324
+ )
325
+ if (scaled !== cropped && cropped !== source) cropped.recycle()
326
+ return scaled
327
+ }
328
+
329
+ private fun barcodeRegions(bitmap: Bitmap): List<BarcodeRegion> {
330
+ val isCloseUp = bitmap.width >= bitmap.height * 2.5
331
+ return if (isCloseUp) {
332
+ listOf(
333
+ BarcodeRegion(0.0, 0.0, 1.0, 1.0, false),
334
+ BarcodeRegion(0.0, 0.0, 1.0, 1.0, true)
335
+ )
336
+ } else {
337
+ listOf(
338
+ BarcodeRegion(0.0, 0.0, 1.0, 1.0, false),
339
+ // Overlapping bands cover full-card photos and the centered close-up
340
+ // guide without assuming a province-specific barcode position.
341
+ BarcodeRegion(0.04, 0.03, 0.92, 0.34, true),
342
+ BarcodeRegion(0.04, 0.27, 0.92, 0.46, true),
343
+ BarcodeRegion(0.04, 0.63, 0.92, 0.34, true)
344
+ )
345
+ }
346
+ }
347
+
348
+ private fun resolveMlKitFormats(requestedFormats: Array<String>): IntArray {
349
+ val formats = requestedFormats.mapNotNull { format ->
350
+ when (format.lowercase()) {
351
+ "pdf-417", "pdf417" -> MlKitBarcode.FORMAT_PDF417
352
+ "code-128", "code128" -> MlKitBarcode.FORMAT_CODE_128
353
+ "code-39", "code39" -> MlKitBarcode.FORMAT_CODE_39
354
+ "code-93", "code93" -> MlKitBarcode.FORMAT_CODE_93
355
+ "codabar" -> MlKitBarcode.FORMAT_CODABAR
356
+ "ean-13", "ean13" -> MlKitBarcode.FORMAT_EAN_13
357
+ "ean-8", "ean8" -> MlKitBarcode.FORMAT_EAN_8
358
+ "itf", "itf-14", "itf14" -> MlKitBarcode.FORMAT_ITF
359
+ "upc-a", "upca" -> MlKitBarcode.FORMAT_UPC_A
360
+ "upc-e", "upce" -> MlKitBarcode.FORMAT_UPC_E
361
+ "qr" -> MlKitBarcode.FORMAT_QR_CODE
362
+ "aztec" -> MlKitBarcode.FORMAT_AZTEC
363
+ "data-matrix", "datamatrix" -> MlKitBarcode.FORMAT_DATA_MATRIX
364
+ else -> null
365
+ }
366
+ }.distinct()
367
+
368
+ return formats.ifEmpty { listOf(MlKitBarcode.FORMAT_ALL_FORMATS) }.toIntArray()
369
+ }
370
+
371
+ private fun createMlKitScanner(requestedFormats: Array<String>): BarcodeScanner {
372
+ val formats = resolveMlKitFormats(requestedFormats)
373
+ val options = BarcodeScannerOptions.Builder()
374
+ .setBarcodeFormats(formats.first(), *formats.drop(1).toIntArray())
375
+ .build()
376
+ return BarcodeScanning.getClient(options)
377
+ }
378
+
379
+ private fun decodeCandidateWithMlKit(scanner: BarcodeScanner, bitmap: Bitmap): String? {
380
+ val barcodes = Tasks.await(
381
+ scanner.process(InputImage.fromBitmap(bitmap, 0)),
382
+ 5,
383
+ TimeUnit.SECONDS
384
+ )
385
+
386
+ for (barcode in barcodes) {
387
+ val text = barcode.rawValue
388
+ ?: barcode.rawBytes?.toString(Charsets.ISO_8859_1)
389
+ if (!text.isNullOrBlank()) return text
390
+ }
391
+ return null
392
+ }
393
+
394
+ private fun decodeBarcodeWithMlKit(imagePath: String, requestedFormats: Array<String>): String? {
395
+ val bitmap = loadBarcodeBitmap(imagePath) ?: return null
396
+ val scanner = createMlKitScanner(requestedFormats)
397
+ return try {
398
+ for (region in barcodeRegions(bitmap)) {
399
+ val candidate = createRegionBitmap(bitmap, region)
400
+ try {
401
+ val decoded = try {
402
+ decodeCandidateWithMlKit(scanner, candidate)
403
+ } catch (_: Exception) {
404
+ null
405
+ }
406
+ if (!decoded.isNullOrBlank()) return decoded
407
+ } finally {
408
+ if (candidate !== bitmap) candidate.recycle()
409
+ }
410
+ }
411
+ null
412
+ } finally {
413
+ scanner.close()
414
+ bitmap.recycle()
415
+ }
416
+ }
417
+
418
+ private fun decodeBarcodeWithZxing(imagePath: String, requestedFormats: Array<String>): String? {
419
+ val bitmap = loadBarcodeBitmap(imagePath) ?: return null
420
+ return try {
421
+ val formats = resolveBarcodeFormats(requestedFormats)
422
+
423
+ for (region in barcodeRegions(bitmap)) {
424
+ val candidate = createRegionBitmap(bitmap, region)
425
+ try {
426
+ val decoded = decodeBitmapCandidate(candidate, formats)
427
+ if (!decoded.isNullOrBlank()) return decoded
428
+ } finally {
429
+ if (candidate !== bitmap) candidate.recycle()
430
+ }
431
+ }
432
+ null
433
+ } finally {
434
+ bitmap.recycle()
435
+ }
436
+ }
437
+
438
+ private fun decodeDenseBarcode(imagePath: String, requestedFormats: Array<String>): String? {
439
+ return decodeBarcodeWithMlKit(imagePath, requestedFormats)
440
+ ?: decodeBarcodeWithZxing(imagePath, requestedFormats)
441
+ }
442
+
28
443
  override fun definition() = ModuleDefinition {
29
444
  Name("SanctumKeySdk")
30
445
 
@@ -57,6 +472,18 @@ class SanctumKeySdkModule : Module() {
57
472
  }
58
473
  }
59
474
 
475
+ AsyncFunction("decodeBarcodeFromImage") { imagePath: String, formats: Array<String>, promise: Promise ->
476
+ try {
477
+ promise.resolve(decodeDenseBarcode(imagePath, formats))
478
+ } catch (error: Exception) {
479
+ promise.reject(
480
+ "BARCODE_DECODE_ERROR",
481
+ error.message ?: "Unable to decode barcode image",
482
+ error
483
+ )
484
+ }
485
+ }
486
+
60
487
  AsyncFunction("openCameraWithInstructions") { instructions: String, promise: Promise ->
61
488
  try {
62
489
  val activity = appContext.currentActivity
@@ -131,4 +558,4 @@ class SanctumKeySdkModule : Module() {
131
558
  Events("onCapture", "onError")
132
559
  }
133
560
  }
134
- }
561
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanctum-key/react-native-sdk",
3
- "version": "1.0.53",
3
+ "version": "1.0.56",
4
4
  "description": "Sanctum Key React Native SDK",
5
5
  "main": "build/src/index.js",
6
6
  "types": "build/src/index.d.ts",
@@ -50,6 +50,7 @@
50
50
  "@zxing/browser": "^0.2.0",
51
51
  "@zxing/library": "^0.22.0",
52
52
  "axios": "^1.11.0",
53
+ "crypto-js": "^4.2.0",
53
54
  "expo-blur": "~15.0.8",
54
55
  "i18n-js": "4.5.1",
55
56
  "iconsax-react-nativejs": "^0.0.8",
@@ -58,6 +59,7 @@
58
59
  "zustand": "^5.0.8"
59
60
  },
60
61
  "devDependencies": {
62
+ "@types/crypto-js": "^4.2.2",
61
63
  "@types/react": "~19.1.0",
62
64
  "copyfiles": "^2.4.1",
63
65
  "expo": "~54.0.0",
@@ -4,6 +4,7 @@ declare class SanctumKeySdkModule extends NativeModule<SanctumKeySdkModuleEvents
4
4
  PI: number;
5
5
  testModule(): Promise<string>;
6
6
  requestCameraPermission(): Promise<boolean>;
7
+ decodeBarcodeFromImage?(imagePath: string, formats: string[]): Promise<string | null>;
7
8
  openCameraWithInstructions(instructions: string): Promise<{
8
9
  success: boolean;
9
10
  path?: string;
@@ -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,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;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 +1 @@
1
- {"version":3,"file":"SanctumKeySdkModule.js","sourceRoot":"","sources":["../../src/SanctumKeySdkModule.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAcxD,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 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;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 +1 @@
1
- {"version":3,"file":"EnhancedCameraView.d.ts","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAU,QAAQ,EAAmC,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAsS5D,CAAC"}
1
+ {"version":3,"file":"EnhancedCameraView.d.ts","sourceRoot":"","sources":["../../../src/components/EnhancedCameraView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmD,MAAM,OAAO,CAAC;AAExE,OAAO,EAAU,QAAQ,EAAmC,MAAM,4BAA4B,CAAC;AAG/F,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGzD,MAAM,WAAW,mBAAoB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC;IACnF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;CAC9E;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAiT5D,CAAC"}