@certiface/sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +966 -0
  3. package/RnSdk.podspec +27 -0
  4. package/android/build.gradle +86 -0
  5. package/android/gradle.properties +6 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/java/br/com/oititec/rn/sdk/RnSdkModule.kt +128 -0
  8. package/android/src/main/java/br/com/oititec/rn/sdk/RnSdkPackage.kt +33 -0
  9. package/android/src/main/java/br/com/oititec/rn/sdk/executor/LivenessExevutor.kt +66 -0
  10. package/android/src/main/java/br/com/oititec/rn/sdk/factories/FacetecThemeFactory.kt +233 -0
  11. package/android/src/main/java/br/com/oititec/rn/sdk/factories/IProovThemeFactory.kt +176 -0
  12. package/android/src/main/java/br/com/oititec/rn/sdk/managers/AssetManager.kt +152 -0
  13. package/android/src/main/java/br/com/oititec/rn/sdk/model/Featues.kt +6 -0
  14. package/android/src/main/java/br/com/oititec/rn/sdk/processors/AssetProcessor.kt +179 -0
  15. package/android/src/main/java/br/com/oititec/rn/sdk/strategy/FacetecStrategy.kt +25 -0
  16. package/android/src/main/java/br/com/oititec/rn/sdk/strategy/IProovStrategy.kt +25 -0
  17. package/android/src/main/java/br/com/oititec/rn/sdk/strategy/LivenessProviderStrategy.kt +16 -0
  18. package/android/src/main/java/br/com/oititec/rn/sdk/theme/FacetecFonts.kt +85 -0
  19. package/android/src/main/java/br/com/oititec/rn/sdk/theme/IProovFonts.kt +52 -0
  20. package/android/src/main/java/br/com/oititec/rn/sdk/utils/AssetProcessor.kt +181 -0
  21. package/android/src/main/res/drawable/backhand_left.xml +20 -0
  22. package/android/src/main/res/drawable/backhand_right.xml +20 -0
  23. package/android/src/main/res/drawable/camera_icon.xml +14 -0
  24. package/android/src/main/res/drawable/close_icon.xml +11 -0
  25. package/android/src/main/res/drawable/error_icon.xml +11 -0
  26. package/android/src/main/res/drawable/neutral_face.xml +11 -0
  27. package/android/src/main/res/drawable/success_icon.xml +11 -0
  28. package/android/src/main/res/font/sixty.ttf +0 -0
  29. package/ios/Extensions/RnSDK+Callbacks.swift +62 -0
  30. package/ios/Extensions/UIColor+Hex.swift +39 -0
  31. package/ios/Resources/Media.xcassets/Contents.json +6 -0
  32. package/ios/Resources/Media.xcassets/shell.imageset/Contents.json +12 -0
  33. package/ios/Resources/Media.xcassets/shell.imageset/shell.png +0 -0
  34. package/ios/Resources/Media.xcassets/test.imageset/Contents.json +12 -0
  35. package/ios/Resources/Media.xcassets/test.imageset/arrow_forward_ios.png +0 -0
  36. package/ios/RnSdk.h +5 -0
  37. package/ios/RnSdk.mm +71 -0
  38. package/ios/RnSdkImpl.swift +91 -0
  39. package/ios/Utils/RnSdkBundle.swift +27 -0
  40. package/ios/Utils/ThemeFactory.swift +424 -0
  41. package/lib/module/@types/result.js +2 -0
  42. package/lib/module/@types/result.js.map +1 -0
  43. package/lib/module/@types/theme.js +41 -0
  44. package/lib/module/@types/theme.js.map +1 -0
  45. package/lib/module/NativeRnSdk.js +5 -0
  46. package/lib/module/NativeRnSdk.js.map +1 -0
  47. package/lib/module/index.js +33 -0
  48. package/lib/module/index.js.map +1 -0
  49. package/lib/module/package.json +1 -0
  50. package/lib/module/utils/AssetProcessor.js +78 -0
  51. package/lib/module/utils/AssetProcessor.js.map +1 -0
  52. package/lib/typescript/package.json +1 -0
  53. package/lib/typescript/src/@types/result.d.ts +17 -0
  54. package/lib/typescript/src/@types/result.d.ts.map +1 -0
  55. package/lib/typescript/src/@types/theme.d.ts +306 -0
  56. package/lib/typescript/src/@types/theme.d.ts.map +1 -0
  57. package/lib/typescript/src/NativeRnSdk.d.ts +9 -0
  58. package/lib/typescript/src/NativeRnSdk.d.ts.map +1 -0
  59. package/lib/typescript/src/index.d.ts +14 -0
  60. package/lib/typescript/src/index.d.ts.map +1 -0
  61. package/lib/typescript/src/utils/AssetProcessor.d.ts +8 -0
  62. package/lib/typescript/src/utils/AssetProcessor.d.ts.map +1 -0
  63. package/package.json +165 -0
  64. package/src/@types/result.ts +19 -0
  65. package/src/@types/theme.ts +346 -0
  66. package/src/NativeRnSdk.ts +18 -0
  67. package/src/index.tsx +54 -0
  68. package/src/utils/AssetProcessor.ts +114 -0
@@ -0,0 +1,52 @@
1
+ package br.com.oititec.rn.sdk.theme
2
+
3
+ import br.com.oiti.manager.exports.IProovFontsKey
4
+ import com.facebook.react.bridge.ReadableMap
5
+
6
+ class IProovFonts(private val fontsBuilder: ReadableMap?) {
7
+
8
+ private val instructionsTitleFont: String =
9
+ "fonts/" + (fontsBuilder?.getString("instructionsTitleFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
10
+
11
+ private val instructionsCaptionFont: String =
12
+ "fonts/" + (fontsBuilder?.getString("instructionsCaptionFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
13
+
14
+ private val instructionsDocumentTypesInstructionsFont: String =
15
+ "fonts/" + (fontsBuilder?.getString("instructionsDocumentTypesInstructionsFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
16
+
17
+ private val instructionsDocumentTipsInstructionsFont: String =
18
+ "fonts/" + (fontsBuilder?.getString("instructionsDocumentTipsInstructionsFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
19
+
20
+ private val instructionsButtonFont: String =
21
+ "fonts/" + (fontsBuilder?.getString("instructionsButtonFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
22
+
23
+ private val permissionTitleFont: String =
24
+ "fonts/" + (fontsBuilder?.getString("permissionTitleFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
25
+
26
+ private val permissionCaptionFont: String =
27
+ "fonts/" + (fontsBuilder?.getString("permissionCaptionFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
28
+
29
+ private val permissionButtonFont: String =
30
+ "fonts/" + (fontsBuilder?.getString("permissionButtonFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
31
+
32
+ private val resultMessageFont: String =
33
+ "fonts/" + (fontsBuilder?.getString("resultMessageFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
34
+
35
+ private val resultRetryButtonFont: String =
36
+ "fonts/" + (fontsBuilder?.getString("resultRetryButtonFont")?.lowercase() ?: "ubuntu_regular") + ".ttf"
37
+
38
+ fun apply(): Map<IProovFontsKey, String> {
39
+ return mapOf(
40
+ IProovFontsKey.INSTRUCTIONS_TITLE_FONT to instructionsTitleFont,
41
+ IProovFontsKey.INSTRUCTIONS_CAPTION_FONT to instructionsCaptionFont,
42
+ IProovFontsKey.INSTRUCTIONS_DOCUMENT_TYPES_INSTRUCTIONS_FONT to instructionsDocumentTypesInstructionsFont,
43
+ IProovFontsKey.INSTRUCTIONS_DOCUMENT_TIPS_INSTRUCTIONS_FONT to instructionsDocumentTipsInstructionsFont,
44
+ IProovFontsKey.INSTRUCTIONS_BUTTON_FONT to instructionsButtonFont,
45
+ IProovFontsKey.PERMISSION_TITLE_FONT to permissionTitleFont,
46
+ IProovFontsKey.PERMISSION_CAPTION_FONT to permissionCaptionFont,
47
+ IProovFontsKey.PERMISSION_BUTTON_FONT to permissionButtonFont,
48
+ IProovFontsKey.RESULT_MESSAGE_FONT to resultMessageFont,
49
+ IProovFontsKey.RESULT_RETRY_BUTTON_FONT to resultRetryButtonFont,
50
+ )
51
+ }
52
+ }
@@ -0,0 +1,181 @@
1
+ package br.com.oititec.rn.sdk.utils
2
+
3
+ import android.content.Context
4
+ import android.content.res.Resources
5
+ import android.graphics.Bitmap
6
+ import android.graphics.BitmapFactory
7
+ import android.graphics.drawable.BitmapDrawable
8
+ import android.graphics.drawable.Drawable
9
+ import android.util.Base64
10
+ import android.util.Log
11
+ import java.io.ByteArrayOutputStream
12
+ import java.io.File
13
+ import java.io.FileOutputStream
14
+ import java.util.UUID
15
+ import java.lang.reflect.Field
16
+
17
+ class AssetProcessor(private val context: Context) {
18
+
19
+ companion object {
20
+ private const val TAG = "AssetProcessor"
21
+ private const val ASSETS_CACHE_DIR = "theme_assets"
22
+ }
23
+
24
+ fun processBase64ToDrawable(base64String: String?, fallbackDrawableRes: Int): Drawable? {
25
+ if (base64String.isNullOrEmpty()) {
26
+ Log.d(TAG, "Base64 string is null or empty, using fallback")
27
+ return context.getDrawable(fallbackDrawableRes)
28
+ }
29
+
30
+ return try {
31
+ Log.d(TAG, "Processing base64 string: ${base64String.take(50)}...")
32
+
33
+ val cleanBase64 = if (base64String.contains("base64,")) {
34
+ base64String.substringAfter("base64,")
35
+ } else {
36
+ base64String
37
+ }
38
+
39
+ val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT)
40
+ Log.d(TAG, "Decoded bytes size: ${decodedBytes.size}")
41
+
42
+ val bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
43
+
44
+ if (bitmap != null) {
45
+ Log.d(TAG, "Successfully created bitmap: ${bitmap.width}x${bitmap.height}")
46
+ BitmapDrawable(context.resources, bitmap)
47
+ } else {
48
+ Log.w(TAG, "Failed to decode bitmap from base64")
49
+ context.getDrawable(fallbackDrawableRes)
50
+ }
51
+ } catch (e: Exception) {
52
+ Log.e(TAG, "Error processing base64 to drawable: ${e.message}")
53
+ e.printStackTrace()
54
+ context.getDrawable(fallbackDrawableRes)
55
+ }
56
+ }
57
+
58
+ fun saveBase64AsDrawableResource(base64String: String?): Int? {
59
+ if (base64String.isNullOrEmpty()) return null
60
+
61
+ return try {
62
+ Log.d(TAG, "Saving base64 as drawable resource...")
63
+
64
+ val cleanBase64 = if (base64String.contains("base64,")) {
65
+ base64String.substringAfter("base64,")
66
+ } else {
67
+ base64String
68
+ }
69
+
70
+ val decodedBytes = Base64.decode(cleanBase64, Base64.DEFAULT)
71
+
72
+ val bitmap = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
73
+
74
+ if (bitmap != null) {
75
+ Log.d(TAG, "Successfully created bitmap from base64: ${bitmap.width}x${bitmap.height}")
76
+
77
+ val cacheDir = File(context.cacheDir, ASSETS_CACHE_DIR)
78
+ if (!cacheDir.exists()) {
79
+ cacheDir.mkdirs()
80
+ }
81
+
82
+ val fileName = "asset_${System.currentTimeMillis()}.png"
83
+ val file = File(cacheDir, fileName)
84
+
85
+ FileOutputStream(file).use { out ->
86
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
87
+ }
88
+
89
+ Log.d(TAG, "Saved asset to: ${file.absolutePath}")
90
+
91
+ System.currentTimeMillis().toInt()
92
+ } else {
93
+ Log.w(TAG, "Failed to create bitmap from base64")
94
+ null
95
+ }
96
+ } catch (e: Exception) {
97
+ Log.e(TAG, "Error saving base64 as drawable resource: ${e.message}")
98
+ e.printStackTrace()
99
+ null
100
+ }
101
+ }
102
+
103
+ fun createDrawableFromCachedFile(resourceId: Int?, fallbackDrawableRes: Int): Drawable? {
104
+ if (resourceId == null) {
105
+ return context.getDrawable(fallbackDrawableRes)
106
+ }
107
+
108
+ return try {
109
+ val cacheDir = File(context.cacheDir, ASSETS_CACHE_DIR)
110
+ val files = cacheDir.listFiles() ?: return context.getDrawable(fallbackDrawableRes)
111
+
112
+ val targetFile = files.find { it.absolutePath.hashCode() == resourceId }
113
+
114
+ if (targetFile?.exists() == true) {
115
+ val bitmap = BitmapFactory.decodeFile(targetFile.absolutePath)
116
+ if (bitmap != null) {
117
+ BitmapDrawable(context.resources, bitmap)
118
+ } else {
119
+ context.getDrawable(fallbackDrawableRes)
120
+ }
121
+ } else {
122
+ Log.w(TAG, "Cached file not found for resource ID: $resourceId")
123
+ context.getDrawable(fallbackDrawableRes)
124
+ }
125
+ } catch (e: Exception) {
126
+ Log.e(TAG, "Error creating drawable from cached file: ${e.message}")
127
+ context.getDrawable(fallbackDrawableRes)
128
+ }
129
+ }
130
+
131
+ fun createDynamicResourceId(drawable: Drawable?): Int? {
132
+ if (drawable == null) return null
133
+
134
+ return try {
135
+ val drawableHash = drawable.hashCode()
136
+
137
+ if (drawable is BitmapDrawable) {
138
+ val bitmap = drawable.bitmap
139
+ if (bitmap != null) {
140
+ val cacheDir = File(context.cacheDir, ASSETS_CACHE_DIR)
141
+ if (!cacheDir.exists()) {
142
+ cacheDir.mkdirs()
143
+ }
144
+
145
+ val fileName = "dynamic_${drawableHash}.png"
146
+ val file = File(cacheDir, fileName)
147
+
148
+ if (!file.exists()) {
149
+ FileOutputStream(file).use { out ->
150
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
151
+ }
152
+ Log.d(TAG, "Created dynamic resource file: ${file.absolutePath}")
153
+ }
154
+
155
+ file.absolutePath.hashCode()
156
+ } else {
157
+ Log.w(TAG, "BitmapDrawable has null bitmap")
158
+ null
159
+ }
160
+ } else {
161
+ Log.d(TAG, "Drawable is not BitmapDrawable, using hash as ID")
162
+ drawableHash
163
+ }
164
+ } catch (e: Exception) {
165
+ Log.e(TAG, "Error creating dynamic resource ID: ${e.message}")
166
+ null
167
+ }
168
+ }
169
+
170
+ fun cleanupCache() {
171
+ try {
172
+ val cacheDir = File(context.cacheDir, ASSETS_CACHE_DIR)
173
+ if (cacheDir.exists()) {
174
+ cacheDir.deleteRecursively()
175
+ }
176
+ } catch (e: Exception) {
177
+ Log.e(TAG, "Error cleaning up cache: ${e.message}")
178
+ }
179
+ }
180
+ }
181
+
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FF000000"
9
+ android:pathData="M13,24c-0.55,0 -1,-0.45 -1,-1V12c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v11C14,23.55 13.55,24 13,24z"/>
10
+ <path
11
+ android:fillColor="#FF000000"
12
+ android:pathData="M9,22c-0.55,0 -1,-0.45 -1,-1V10c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v11C10,21.55 9.55,22 9,22z"/>
13
+ <path
14
+ android:fillColor="#FF000000"
15
+ android:pathData="M17,22c-0.55,0 -1,-0.45 -1,-1V14c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v7C18,21.55 17.55,22 17,22z"/>
16
+ <path
17
+ android:fillColor="#FF000000"
18
+ android:pathData="M5,20c-0.55,0 -1,-0.45 -1,-1V8c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v11C6,19.55 5.55,20 5,20z"/>
19
+ </vector>
20
+
@@ -0,0 +1,20 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FF000000"
9
+ android:pathData="M11,24c0.55,0 1,-0.45 1,-1V12c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v11C10,23.55 10.45,24 11,24z"/>
10
+ <path
11
+ android:fillColor="#FF000000"
12
+ android:pathData="M15,22c0.55,0 1,-0.45 1,-1V10c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v11C14,21.55 14.45,22 15,22z"/>
13
+ <path
14
+ android:fillColor="#FF000000"
15
+ android:pathData="M7,22c0.55,0 1,-0.45 1,-1V14c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v7C6,21.55 6.45,22 7,22z"/>
16
+ <path
17
+ android:fillColor="#FF000000"
18
+ android:pathData="M19,20c0.55,0 1,-0.45 1,-1V8c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v11C18,19.55 18.45,20 19,20z"/>
19
+ </vector>
20
+
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FF000000"
9
+ android:pathData="M12,15.2c-1.25,0 -2.27,-1.02 -2.27,-2.27c0,-1.25 1.02,-2.27 2.27,-2.27c1.25,0 2.27,1.02 2.27,2.27C14.27,14.18 13.25,15.2 12,15.2z"/>
10
+ <path
11
+ android:fillColor="#FF000000"
12
+ android:pathData="M9,2L7.17,4H4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2H9zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5s5,2.24 5,5S14.76,17 12,17z"/>
13
+ </vector>
14
+
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FF000000"
9
+ android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
10
+ </vector>
11
+
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FFF44336"
9
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
10
+ </vector>
11
+
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FFFF9800"
9
+ android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM15.5,8C16.33,8 17,8.67 17,9.5S16.33,11 15.5,11 14,10.33 14,9.5 14.67,8 15.5,8zM8.5,8C9.33,8 10,8.67 10,9.5S9.33,11 8.5,11 7,10.33 7,9.5 7.67,8 8.5,8zM12,17.5c-2.33,0 -4.31,-1.46 -5.11,-3.5h10.22C16.31,16.04 14.33,17.5 12,17.5z"/>
10
+ </vector>
11
+
@@ -0,0 +1,11 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <vector xmlns:android="http://schemas.android.com/apk/res/android"
3
+ android:width="24dp"
4
+ android:height="24dp"
5
+ android:viewportWidth="24"
6
+ android:viewportHeight="24">
7
+ <path
8
+ android:fillColor="#FF4CAF50"
9
+ android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
10
+ </vector>
11
+
@@ -0,0 +1,62 @@
1
+ //
2
+ // RnSDK+Callbacks.swift
3
+ // Pods
4
+ //
5
+ // Created by Gabriel Catelli Goulart on 21/07/25.
6
+ //
7
+
8
+ import CertifaceSDK
9
+
10
+ extension RnSdkImpl: LivenessCallback {
11
+ public func onSuccess(_ resultData: LivenessResult) {
12
+ let response: [String: Any] = [
13
+ "status": "success",
14
+ "result": [
15
+ "valid": resultData.valid,
16
+ "codID": resultData.codId as Any,
17
+ "cause": "",
18
+ "protocol": resultData.protocol as Any,
19
+ "scanResultBlob": "",
20
+ ],
21
+ ]
22
+
23
+ if let jsonData = try? JSONSerialization.data(withJSONObject: response),
24
+ let jsonString = String(data: jsonData, encoding: .utf8)
25
+ {
26
+ onSuccessCallback?(jsonString)
27
+ } else {
28
+ onErrorCallback?("Failed to serialize response")
29
+ }
30
+
31
+ onSuccessCallback = nil
32
+ onErrorCallback = nil
33
+ }
34
+
35
+ public func onError(_ error: LivenessError) {
36
+ let response: [String: Any] = [
37
+ "status": "error",
38
+ "message": "[\(error.code)]: \(error.message)",
39
+ ]
40
+
41
+ if let jsonData = try? JSONSerialization.data(withJSONObject: response),
42
+ let jsonString = String(data: jsonData, encoding: .utf8)
43
+ {
44
+ onErrorCallback?(jsonString)
45
+ } else {
46
+ onErrorCallback?("Failed to serialize error response")
47
+ }
48
+
49
+ onSuccessCallback = nil
50
+ onErrorCallback = nil
51
+ }
52
+
53
+ func getRootViewController() -> UIViewController? {
54
+ let windowScene = UIApplication.shared.connectedScenes
55
+ .compactMap { $0 as? UIWindowScene }
56
+ .first { $0.activationState == .foregroundActive }
57
+
58
+ let keyWindow = windowScene?.windows.first { $0.isKeyWindow }
59
+
60
+ return keyWindow?.rootViewController
61
+ }
62
+ }
@@ -0,0 +1,39 @@
1
+ //
2
+ // UIColor+Hex.swift
3
+ // RnSdk
4
+ //
5
+ // Created by Gabriel Catelli Goulart on 01/08/25.
6
+ //
7
+
8
+ import UIKit
9
+
10
+ extension UIColor {
11
+ convenience init?(hex: String) {
12
+ let hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines)
13
+ let scanner = Scanner(
14
+ string: hexString.hasPrefix("#") ? String(hexString.dropFirst()) : hexString)
15
+
16
+ var color: UInt64 = 0
17
+
18
+ guard scanner.scanHexInt64(&color) else {
19
+ return nil
20
+ }
21
+
22
+ if hexString.count == 7 || (hexString.hasPrefix("#") && hexString.count == 7) {
23
+ let red = Double((color & 0xFF0000) >> 16) / 255.0
24
+ let green = Double((color & 0x00FF00) >> 8) / 255.0
25
+ let blue = Double(color & 0x0000FF) / 255.0
26
+
27
+ self.init(red: red, green: green, blue: blue, alpha: 1.0)
28
+ } else if hexString.count == 9 || (hexString.hasPrefix("#") && hexString.count == 9) {
29
+ let red = Double((color & 0xFF00_0000) >> 24) / 255.0
30
+ let green = Double((color & 0x00FF_0000) >> 16) / 255.0
31
+ let blue = Double((color & 0x0000_FF00) >> 8) / 255.0
32
+ let alpha = Double(color & 0x0000_00FF) / 255.0
33
+
34
+ self.init(red: red, green: green, blue: blue, alpha: alpha)
35
+ } else {
36
+ return nil
37
+ }
38
+ }
39
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "info" : {
3
+ "author" : "xcode",
4
+ "version" : 1
5
+ }
6
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "images" : [
3
+ {
4
+ "filename" : "shell.png",
5
+ "idiom" : "universal"
6
+ }
7
+ ],
8
+ "info" : {
9
+ "author" : "xcode",
10
+ "version" : 1
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "images" : [
3
+ {
4
+ "filename" : "arrow_forward_ios.png",
5
+ "idiom" : "universal"
6
+ }
7
+ ],
8
+ "info" : {
9
+ "author" : "xcode",
10
+ "version" : 1
11
+ }
12
+ }
package/ios/RnSdk.h ADDED
@@ -0,0 +1,5 @@
1
+ #import <RnSdkSpec/RnSdkSpec.h>
2
+
3
+ @interface RnSdk : NSObject <NativeRnSdkSpec>
4
+
5
+ @end
package/ios/RnSdk.mm ADDED
@@ -0,0 +1,71 @@
1
+ #import "RnSdk.h"
2
+ #import "RnSdk-Swift.h"
3
+ #import <AVFoundation/AVFoundation.h>
4
+
5
+ @implementation RnSdk {
6
+ RnSdkImpl *moduleImpl;
7
+ }
8
+
9
+ - (instancetype)init {
10
+ self = [super init];
11
+ if (self) {
12
+ moduleImpl = [RnSdkImpl new];
13
+ }
14
+ return self;
15
+ }
16
+
17
+ RCT_EXPORT_MODULE()
18
+
19
+ - (void)checkCameraPermission:(RCTPromiseResolveBlock)resolve
20
+ reject:(RCTPromiseRejectBlock)reject {
21
+ AVAuthorizationStatus status =
22
+ [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
23
+
24
+ resolve(@(status == AVAuthorizationStatusAuthorized));
25
+ }
26
+
27
+ - (void)requestCameraPermission:(RCTPromiseResolveBlock)resolve
28
+ reject:(RCTPromiseRejectBlock)reject {
29
+ AVAuthorizationStatus status =
30
+ [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
31
+
32
+ if (status == AVAuthorizationStatusAuthorized) {
33
+ resolve(@YES);
34
+ return;
35
+ }
36
+
37
+ [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo
38
+ completionHandler:^(BOOL granted) {
39
+ dispatch_async(dispatch_get_main_queue(), ^{
40
+ resolve(@(granted));
41
+ });
42
+ }];
43
+ }
44
+
45
+ - (void)startJourney:(NSString *)appKey
46
+ environment:(NSString *)environment
47
+ provider:(NSString *)provider
48
+ onSuccess:(RCTResponseSenderBlock)onSuccess
49
+ onError:(RCTResponseSenderBlock)onError
50
+ isCustomEnabled:(NSNumber *)isCustomEnabled
51
+ theme:(NSDictionary *)theme {
52
+ BOOL customEnabled = isCustomEnabled ? [isCustomEnabled boolValue] : NO;
53
+ [moduleImpl startJourneyWithAppKey:appKey
54
+ environment:environment
55
+ provider:provider
56
+ isCustomEnabled:customEnabled
57
+ theme:theme
58
+ onSuccess:^(NSString *_Nonnull result) {
59
+ onSuccess(@[ result ]);
60
+ }
61
+ onError:^(NSString *_Nonnull error) {
62
+ onError(@[ error ]);
63
+ }];
64
+ }
65
+
66
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
67
+ (const facebook::react::ObjCTurboModule::InitParams &)params {
68
+ return std::make_shared<facebook::react::NativeRnSdkSpecJSI>(params);
69
+ }
70
+
71
+ @end
@@ -0,0 +1,91 @@
1
+ //
2
+ // RnSdkImpl.swift
3
+ // RnSdk
4
+ //
5
+ // Created by Gabriel Catelli Goulart on 21/07/25.
6
+ //
7
+
8
+ import CertifaceSDK
9
+ import UIKit
10
+
11
+ @objc public class RnSdkImpl: NSObject {
12
+ var onSuccessCallback: ((String) -> Void)?
13
+ var onErrorCallback: ((String) -> Void)?
14
+
15
+ @objc public func startJourney(
16
+ appKey: String,
17
+ environment: String,
18
+ provider: String,
19
+ isCustomEnabled: Bool,
20
+ theme: [String: Any]?,
21
+ onSuccess: @escaping (String) -> Void,
22
+ onError: @escaping (String) -> Void
23
+ ) {
24
+ print(
25
+ "AppKey: \(appKey), Environment: \(environment), Provider: \(provider), CustomEnabled: \(isCustomEnabled)"
26
+ )
27
+ if let themeData = theme {
28
+ print("Theme: \(themeData)")
29
+ }
30
+
31
+ self.onSuccessCallback = onSuccess
32
+ self.onErrorCallback = onError
33
+
34
+ let sdkEnvironment: CertifaceSDK.Environment
35
+ if environment == "PRD" {
36
+ sdkEnvironment = .prd
37
+ } else {
38
+ sdkEnvironment = .hml
39
+ }
40
+
41
+ let livenessProvider: LivenessProvider
42
+ if provider == "FACETEC" {
43
+ livenessProvider = .facetec
44
+ } else if provider == "IPROOV" {
45
+ livenessProvider = .iproov
46
+ } else {
47
+ onError("Invalid provider: \(provider)")
48
+ return
49
+ }
50
+
51
+ var facetecCustomization = FacetecCustomization.builder().build()
52
+ var iproovCustomization = IProovCustomization.builder().build()
53
+
54
+ var showInstructionsScreen = true
55
+
56
+ if isCustomEnabled {
57
+ switch livenessProvider {
58
+ case .facetec:
59
+ facetecCustomization = ThemeFactory.createFacetecCustomization(from: theme)
60
+ case .iproov:
61
+ iproovCustomization = ThemeFactory.createIProovCustomization(from: theme)
62
+ @unknown default:
63
+ break
64
+ }
65
+
66
+ if let themeData = theme,
67
+ let instructionsTheme = themeData["instructions"] as? [String: Any],
68
+ let configuration = instructionsTheme["configuration"] as? [String: Any],
69
+ let showInstruction = configuration["showInstructionScreen"] as? Bool {
70
+ showInstructionsScreen = showInstruction
71
+ }
72
+ }
73
+
74
+ let options = LivenessManagerOptions
75
+ .builder(appKey: appKey, environment: sdkEnvironment)
76
+ .setShowInstructionsScreen(showInstructionsScreen)
77
+ .setFacetecCustomization(facetecCustomization)
78
+ .setIProovCustomization(iproovCustomization)
79
+ .build()
80
+ let manager = CertifaceSDKFactory.createLivenessManager(for: livenessProvider)
81
+
82
+ DispatchQueue.main.async { [weak self] in
83
+ guard let self, let viewController = getRootViewController() else {
84
+ onError("Cannot get rootViewController")
85
+ return
86
+ }
87
+
88
+ manager.start(at: viewController, options: options, callback: self)
89
+ }
90
+ }
91
+ }