@myazahq/kyc-sdk-react-native 2.3.0 → 2.5.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.
- package/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +75 -0
- package/ios/HybridMyazaFaceDetector.swift +8 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
- package/package.json +1 -2
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/BrandBar.tsx +137 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +25 -24
- package/src/components/DocumentReviewSide.tsx +34 -14
- package/src/components/Icon.tsx +14 -0
- package/src/components/KycSheet.tsx +39 -140
- package/src/components/MediaSourceSheet.tsx +7 -37
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaDateField.tsx +6 -20
- package/src/components/MyazaInput.tsx +23 -4
- package/src/components/MyazaSelect.tsx +17 -40
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +37 -4
- package/src/components/SandboxBanner.tsx +92 -0
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepHeader.tsx +15 -2
- package/src/components/StepView.tsx +26 -0
- package/src/components/glass/ChromeGlass.tsx +65 -0
- package/src/components/glass/FloatingSheet.tsx +242 -0
- package/src/components/glass/GlassSheet.tsx +50 -0
- package/src/components/glass/GlassSurface.tsx +31 -3
- package/src/components/stepHeaderMeta.tsx +3 -1
- package/src/components/viewfinder/ImmersiveBottomBar.tsx +28 -17
- package/src/components/viewfinder/ImmersiveControls.tsx +26 -14
- package/src/components/viewfinder/ViewfinderControls.tsx +29 -7
- package/src/config/business.ts +6 -6
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +14 -3
- package/src/config/keyPeople.ts +111 -7
- package/src/config/keyPeopleCorporate.ts +19 -0
- package/src/config/keyPeopleOwnerTypes.ts +12 -0
- package/src/config/keyPeopleOwners.ts +26 -0
- package/src/config/keyPeoplePrefill.ts +138 -0
- package/src/config/keyPeopleSectionDefs.ts +65 -0
- package/src/config/keyPeopleSections.ts +167 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +24 -4
- package/src/config/theme.ts +8 -2
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +5 -0
- package/src/emrtd/activeAuth.ts +100 -0
- package/src/emrtd/extras.ts +76 -0
- package/src/emrtd/files.ts +5 -0
- package/src/emrtd/index.ts +1 -0
- package/src/emrtd/read.ts +8 -1
- package/src/emrtd/session.ts +41 -0
- package/src/emrtd/stages.ts +11 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +109 -0
- package/src/screens/BusinessDetailsStep.tsx +131 -145
- package/src/screens/BusinessDocumentSlot.tsx +2 -3
- package/src/screens/BusinessDocumentsStep.tsx +1 -1
- package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
- package/src/screens/BusinessPickedCard.tsx +86 -0
- package/src/screens/BusinessPickedSection.tsx +51 -0
- package/src/screens/BusinessRegistryPickers.tsx +67 -0
- package/src/screens/BusinessSandboxToggle.tsx +114 -0
- package/src/screens/BusinessSearch.tsx +199 -0
- package/src/screens/BusinessSearchResults.tsx +115 -0
- package/src/screens/CompanyInfoFields.tsx +86 -51
- package/src/screens/ConsentStep.tsx +31 -0
- package/src/screens/ContactDestinationField.tsx +1 -0
- package/src/screens/ContactVerificationStep.tsx +34 -3
- package/src/screens/IdTypeStep.tsx +12 -5
- package/src/screens/KeepLinksSheet.tsx +88 -0
- package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
- package/src/screens/KeyPeopleAwaitList.tsx +126 -0
- package/src/screens/KeyPeoplePending.tsx +154 -0
- package/src/screens/KeyPeopleSection.tsx +162 -0
- package/src/screens/KeyPeopleSectionsList.tsx +103 -0
- package/src/screens/KeyPeopleUboExemption.tsx +72 -0
- package/src/screens/KeyPersonCard.tsx +36 -4
- package/src/screens/KeyPersonForm.tsx +195 -36
- package/src/screens/KeyPersonKindToggle.tsx +68 -0
- package/src/screens/KeyPersonOwners.tsx +109 -0
- package/src/screens/KeyPersonRoleChips.tsx +68 -0
- package/src/screens/KeyPersonSheet.tsx +92 -108
- package/src/screens/LivenessStep.tsx +18 -0
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/ProofOfAddressStep.tsx +6 -3
- package/src/screens/SubmittedStep.tsx +66 -10
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/services/api-types.ts +158 -2
- package/src/services/api-verify-types.ts +53 -1
- package/src/services/api.ts +104 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/workflowGate.ts +1 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +15 -0
- package/src/store/kycStore.ts +241 -14
- package/src/store/serverConfig.ts +2 -0
- package/src/store/session.ts +208 -0
- package/src/store/state.ts +155 -2
- package/src/store/submit.ts +63 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +29 -3
- package/src/types/verification.ts +1 -1
- package/src/types/workflow.ts +15 -0
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
package/android/build.gradle
CHANGED
|
@@ -109,8 +109,40 @@ dependencies {
|
|
|
109
109
|
// CameraX core — for the ImageProxy / ExperimentalGetImage APIs.
|
|
110
110
|
compileOnly 'androidx.camera:camera-core:1.3.4'
|
|
111
111
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
//
|
|
112
|
+
// ── ML Kit: models fetched on demand by default ──────────────────────────
|
|
113
|
+
//
|
|
114
|
+
// Two variants of the same API exist. The `com.google.mlkit:*` artifacts embed
|
|
115
|
+
// the models in the APK; the `com.google.android.gms:play-services-mlkit-*`
|
|
116
|
+
// ones fetch them through Google Play Services on first use.
|
|
117
|
+
//
|
|
118
|
+
// Measured on a real integrator's release APK, the bundled pair cost
|
|
119
|
+
// **18.5 MB per device** (arm64: ocr_pipeline 10.55 MB + face_detector 7.95 MB)
|
|
120
|
+
// plus the .tflite files in assets/, which ship to EVERY device because assets
|
|
121
|
+
// are not split by ABI. Unbundled, that is ~0.4 MB of shims.
|
|
122
|
+
//
|
|
123
|
+
// The trade-off is real and is why the flag exists: the unbundled variants need
|
|
124
|
+
// Google Play Services, so they do not work on Huawei or AOSP builds without
|
|
125
|
+
// GMS. An org shipping to those devices sets `myazaKycBundledMlKit = true` in
|
|
126
|
+
// its root build.gradle ext and gets today's fully-offline behaviour back.
|
|
127
|
+
//
|
|
128
|
+
// Availability is NOT inferred per frame — `isModelReady()` is checked once
|
|
129
|
+
// before the camera opens (see HybridMyazaFaceDetector.kt), because a missing
|
|
130
|
+
// model would otherwise be indistinguishable from "no face in frame".
|
|
131
|
+
if (safeExtGet('myazaKycBundledMlKit', false).toString().toBoolean()) {
|
|
132
|
+
implementation 'com.google.mlkit:face-detection:16.1.6'
|
|
133
|
+
} else {
|
|
134
|
+
implementation 'com.google.android.gms:play-services-mlkit-face-detection:17.1.0'
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Text recognition is STILL BUNDLED, deliberately, and it is the larger of the
|
|
138
|
+
// two (10.55 MB vs 7.95 MB on arm64) — so this is the bigger half of the win,
|
|
139
|
+
// deferred rather than abandoned.
|
|
140
|
+
//
|
|
141
|
+
// Unbundling it needs the same isModelReady()/prepareModel() contract the face
|
|
142
|
+
// detector just gained. Without that, a missing OCR model is indistinguishable
|
|
143
|
+
// from "no text in frame": document auto-capture would simply never fire and
|
|
144
|
+
// MRZ scanning would never produce a key for the chip read, with nothing shown
|
|
145
|
+
// to the user. Shipping the dependency swap ahead of the availability handling
|
|
146
|
+
// would trade 10 MB for a silent failure on every fresh install.
|
|
115
147
|
implementation 'com.google.mlkit:text-recognition:16.0.1'
|
|
116
148
|
}
|
|
@@ -9,6 +9,10 @@ import com.google.mlkit.vision.face.Face
|
|
|
9
9
|
import com.google.mlkit.vision.face.FaceDetection
|
|
10
10
|
import com.google.mlkit.vision.face.FaceDetector
|
|
11
11
|
import com.google.mlkit.vision.face.FaceDetectorOptions
|
|
12
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstall
|
|
13
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstallRequest
|
|
14
|
+
import com.margelo.nitro.NitroModules
|
|
15
|
+
import com.margelo.nitro.core.Promise
|
|
12
16
|
import java.util.concurrent.CountDownLatch
|
|
13
17
|
import java.util.concurrent.TimeUnit
|
|
14
18
|
|
|
@@ -39,6 +43,67 @@ class HybridMyazaFaceDetector : HybridMyazaFaceDetectorSpec() {
|
|
|
39
43
|
.build(),
|
|
40
44
|
)
|
|
41
45
|
|
|
46
|
+
// ── Model availability ───────────────────────────────────────────────────
|
|
47
|
+
//
|
|
48
|
+
// The default build fetches ML Kit's models through Play Services instead of
|
|
49
|
+
// bundling them (android/build.gradle), which is what keeps ~18.5 MB per
|
|
50
|
+
// device out of the APK. The cost is a window where detection cannot run:
|
|
51
|
+
// first launch before the download completes, or a device with no GMS at all.
|
|
52
|
+
//
|
|
53
|
+
// `detectFace` CANNOT report that. Its only channel is FaceResult, where a
|
|
54
|
+
// missing model and an empty frame are both `faceCount: 0` — so a user on a
|
|
55
|
+
// fresh install would watch "position your face" forever with nothing to
|
|
56
|
+
// explain it. Hence a separate, explicit contract, checked before the camera
|
|
57
|
+
// opens rather than inferred per frame.
|
|
58
|
+
//
|
|
59
|
+
// Cached rather than queried live: `areModulesAvailable` is asynchronous and
|
|
60
|
+
// `isModelReady` is a synchronous Nitro call, so blocking the worklet thread
|
|
61
|
+
// to answer it would be worse than the problem. The flag starts false and is
|
|
62
|
+
// set by prepareModel(), which the SDK primes at flow start.
|
|
63
|
+
@Volatile private var modelReady = false
|
|
64
|
+
|
|
65
|
+
override fun isModelReady(): Boolean = modelReady
|
|
66
|
+
|
|
67
|
+
override fun prepareModel(): Promise<Boolean> = Promise.async {
|
|
68
|
+
if (modelReady) return@async true
|
|
69
|
+
|
|
70
|
+
val ctx = NitroModules.applicationContext
|
|
71
|
+
?: return@async false // No context — cannot ask Play Services anything.
|
|
72
|
+
|
|
73
|
+
val latch = CountDownLatch(1)
|
|
74
|
+
var ok = false
|
|
75
|
+
try {
|
|
76
|
+
val client = ModuleInstall.getClient(ctx)
|
|
77
|
+
// areModulesAvailable() answers "is it already here"; deferredInstall()
|
|
78
|
+
// asks Play Services to fetch it in the background if not. Requesting the
|
|
79
|
+
// install unconditionally is simpler AND correct — it is a no-op when the
|
|
80
|
+
// module is already present, and starting it early is the entire point.
|
|
81
|
+
client.areModulesAvailable(detector)
|
|
82
|
+
.addOnSuccessListener { response ->
|
|
83
|
+
ok = response.areModulesAvailable()
|
|
84
|
+
if (!ok) {
|
|
85
|
+
client.deferredInstall(
|
|
86
|
+
ModuleInstallRequest.newBuilder().addApi(detector).build(),
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
latch.countDown()
|
|
90
|
+
}
|
|
91
|
+
.addOnFailureListener {
|
|
92
|
+
// Thrown on devices without Google Play Services at all (Huawei, bare
|
|
93
|
+
// AOSP). Not an error to retry — it will never succeed on this device.
|
|
94
|
+
// Such orgs should build with `myazaKycBundledMlKit = true`.
|
|
95
|
+
ok = false
|
|
96
|
+
latch.countDown()
|
|
97
|
+
}
|
|
98
|
+
latch.await(MODEL_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
99
|
+
} catch (_: Throwable) {
|
|
100
|
+
ok = false
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
modelReady = ok
|
|
104
|
+
ok
|
|
105
|
+
}
|
|
106
|
+
|
|
42
107
|
@ExperimentalGetImage
|
|
43
108
|
override fun detectFace(frame: HybridFrameSpec): FaceResult {
|
|
44
109
|
// Same access pattern as iOS (cast to the public NativeFrame): VisionCamera's
|
|
@@ -218,4 +283,14 @@ class HybridMyazaFaceDetector : HybridMyazaFaceDetectorSpec() {
|
|
|
218
283
|
faceG = -1.0,
|
|
219
284
|
faceB = -1.0,
|
|
220
285
|
)
|
|
286
|
+
|
|
287
|
+
private companion object {
|
|
288
|
+
/**
|
|
289
|
+
* Bound on the availability query. It is a local Play Services call, not the
|
|
290
|
+
* model download — the download runs in the background afterwards. Generous
|
|
291
|
+
* enough for a cold Play Services process, short enough that a wedged one
|
|
292
|
+
* cannot stall flow start.
|
|
293
|
+
*/
|
|
294
|
+
const val MODEL_CHECK_TIMEOUT_MS = 3_000L
|
|
295
|
+
}
|
|
221
296
|
}
|
|
@@ -28,6 +28,14 @@ import NitroModules
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
29
|
|
|
30
30
|
final class HybridMyazaFaceDetector: HybridMyazaFaceDetectorSpec {
|
|
31
|
+
// Apple Vision ships with the OS — there is no model to fetch, no Play
|
|
32
|
+
// Services equivalent, and no window in which detection is unavailable. These
|
|
33
|
+
// exist only because Android fetches ML Kit's models on demand; iOS answers
|
|
34
|
+
// "ready" unconditionally so the shared TS flow needs no platform branch.
|
|
35
|
+
func isModelReady() throws -> Bool { true }
|
|
36
|
+
|
|
37
|
+
func prepareModel() throws -> Promise<Bool> { Promise.resolved(withResult: true) }
|
|
38
|
+
|
|
31
39
|
func detectFace(frame: any HybridFrameSpec) throws -> FaceResult {
|
|
32
40
|
guard
|
|
33
41
|
let nativeFrame = frame as? NativeFrame,
|
|
@@ -14,6 +14,8 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
14
14
|
|
|
15
15
|
#include "FaceResult.hpp"
|
|
16
16
|
#include "JFaceResult.hpp"
|
|
17
|
+
#include <NitroModules/Promise.hpp>
|
|
18
|
+
#include <NitroModules/JPromise.hpp>
|
|
17
19
|
#include <memory>
|
|
18
20
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
19
21
|
#include <VisionCamera/JHybridFrameSpec.hpp>
|
|
@@ -56,5 +58,26 @@ namespace margelo::nitro::myazakyc {
|
|
|
56
58
|
auto __result = method(_javaPart, std::dynamic_pointer_cast<margelo::nitro::camera::JHybridFrameSpec>(frame)->getJavaPart());
|
|
57
59
|
return __result->toCpp();
|
|
58
60
|
}
|
|
61
|
+
bool JHybridMyazaFaceDetectorSpec::isModelReady() {
|
|
62
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("isModelReady");
|
|
63
|
+
auto __result = method(_javaPart);
|
|
64
|
+
return static_cast<bool>(__result);
|
|
65
|
+
}
|
|
66
|
+
std::shared_ptr<Promise<bool>> JHybridMyazaFaceDetectorSpec::prepareModel() {
|
|
67
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("prepareModel");
|
|
68
|
+
auto __result = method(_javaPart);
|
|
69
|
+
return [&]() {
|
|
70
|
+
auto __promise = Promise<bool>::create();
|
|
71
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
72
|
+
auto __result = jni::static_ref_cast<jni::JBoolean>(__boxedResult);
|
|
73
|
+
__promise->resolve(static_cast<bool>(__result->value()));
|
|
74
|
+
});
|
|
75
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
76
|
+
jni::JniException __jniError(__throwable);
|
|
77
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
78
|
+
});
|
|
79
|
+
return __promise;
|
|
80
|
+
}();
|
|
81
|
+
}
|
|
59
82
|
|
|
60
83
|
} // namespace margelo::nitro::myazakyc
|
|
@@ -55,6 +55,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
55
55
|
public:
|
|
56
56
|
// Methods
|
|
57
57
|
FaceResult detectFace(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) override;
|
|
58
|
+
bool isModelReady() override;
|
|
59
|
+
std::shared_ptr<Promise<bool>> prepareModel() override;
|
|
58
60
|
|
|
59
61
|
private:
|
|
60
62
|
jni::global_ref<JHybridMyazaFaceDetectorSpec::JavaPart> _javaPart;
|
package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt
CHANGED
|
@@ -11,6 +11,7 @@ import androidx.annotation.Keep
|
|
|
11
11
|
import com.facebook.jni.HybridData
|
|
12
12
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
13
13
|
import com.margelo.nitro.camera.HybridFrameSpec
|
|
14
|
+
import com.margelo.nitro.core.Promise
|
|
14
15
|
import com.margelo.nitro.core.HybridObject
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -32,6 +33,14 @@ abstract class HybridMyazaFaceDetectorSpec: HybridObject() {
|
|
|
32
33
|
@DoNotStrip
|
|
33
34
|
@Keep
|
|
34
35
|
abstract fun detectFace(frame: com.margelo.nitro.camera.HybridFrameSpec): FaceResult
|
|
36
|
+
|
|
37
|
+
@DoNotStrip
|
|
38
|
+
@Keep
|
|
39
|
+
abstract fun isModelReady(): Boolean
|
|
40
|
+
|
|
41
|
+
@DoNotStrip
|
|
42
|
+
@Keep
|
|
43
|
+
abstract fun prepareModel(): Promise<Boolean>
|
|
35
44
|
|
|
36
45
|
// Default implementation of `HybridObject.toString()`
|
|
37
46
|
override fun toString(): String {
|
|
@@ -84,6 +84,14 @@ namespace margelo::nitro::myazakyc::bridge::swift {
|
|
|
84
84
|
return margelo::nitro::camera::bridge::swift::get_std__shared_ptr_HybridFrameSpec_(cppType);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// pragma MARK: std::function<void(bool /* result */)>
|
|
88
|
+
Func_void_bool create_Func_void_bool(void* NON_NULL swiftClosureWrapper) noexcept {
|
|
89
|
+
auto swiftClosure = KycSdkReactNative::Func_void_bool::fromUnsafe(swiftClosureWrapper);
|
|
90
|
+
return [swiftClosure = std::move(swiftClosure)](bool result) mutable -> void {
|
|
91
|
+
swiftClosure.call(result);
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
87
95
|
// pragma MARK: std::shared_ptr<HybridMyazaFaceDetectorSpec>
|
|
88
96
|
std::shared_ptr<HybridMyazaFaceDetectorSpec> create_std__shared_ptr_HybridMyazaFaceDetectorSpec_(void* NON_NULL swiftUnsafePointer) noexcept {
|
|
89
97
|
KycSdkReactNative::HybridMyazaFaceDetectorSpec_cxx swiftPart = KycSdkReactNative::HybridMyazaFaceDetectorSpec_cxx::fromUnsafe(swiftUnsafePointer);
|
|
@@ -303,6 +303,40 @@ namespace margelo::nitro::myazakyc::bridge::swift {
|
|
|
303
303
|
using std__weak_ptr_margelo__nitro__camera__HybridFrameSpec_ = std::weak_ptr<margelo::nitro::camera::HybridFrameSpec>;
|
|
304
304
|
inline std__weak_ptr_margelo__nitro__camera__HybridFrameSpec_ weakify_std__shared_ptr_margelo__nitro__camera__HybridFrameSpec_(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& strong) noexcept { return strong; }
|
|
305
305
|
|
|
306
|
+
// pragma MARK: std::shared_ptr<Promise<bool>>
|
|
307
|
+
/**
|
|
308
|
+
* Specialized version of `std::shared_ptr<Promise<bool>>`.
|
|
309
|
+
*/
|
|
310
|
+
using std__shared_ptr_Promise_bool__ = std::shared_ptr<Promise<bool>>;
|
|
311
|
+
inline std::shared_ptr<Promise<bool>> create_std__shared_ptr_Promise_bool__() noexcept {
|
|
312
|
+
return Promise<bool>::create();
|
|
313
|
+
}
|
|
314
|
+
inline PromiseHolder<bool> wrap_std__shared_ptr_Promise_bool__(std::shared_ptr<Promise<bool>> promise) noexcept {
|
|
315
|
+
return PromiseHolder<bool>(std::move(promise));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// pragma MARK: std::function<void(bool /* result */)>
|
|
319
|
+
/**
|
|
320
|
+
* Specialized version of `std::function<void(bool)>`.
|
|
321
|
+
*/
|
|
322
|
+
using Func_void_bool = std::function<void(bool /* result */)>;
|
|
323
|
+
/**
|
|
324
|
+
* Wrapper class for a `std::function<void(bool / * result * /)>`, this can be used from Swift.
|
|
325
|
+
*/
|
|
326
|
+
class Func_void_bool_Wrapper final {
|
|
327
|
+
public:
|
|
328
|
+
explicit Func_void_bool_Wrapper(std::function<void(bool /* result */)>&& func): _function(std::make_unique<std::function<void(bool /* result */)>>(std::move(func))) {}
|
|
329
|
+
inline void call(bool result) const noexcept {
|
|
330
|
+
_function->operator()(result);
|
|
331
|
+
}
|
|
332
|
+
private:
|
|
333
|
+
std::unique_ptr<std::function<void(bool /* result */)>> _function;
|
|
334
|
+
} SWIFT_NONCOPYABLE;
|
|
335
|
+
Func_void_bool create_Func_void_bool(void* NON_NULL swiftClosureWrapper) noexcept;
|
|
336
|
+
inline Func_void_bool_Wrapper wrap_Func_void_bool(Func_void_bool value) noexcept {
|
|
337
|
+
return Func_void_bool_Wrapper(std::move(value));
|
|
338
|
+
}
|
|
339
|
+
|
|
306
340
|
// pragma MARK: std::shared_ptr<HybridMyazaFaceDetectorSpec>
|
|
307
341
|
/**
|
|
308
342
|
* Specialized version of `std::shared_ptr<HybridMyazaFaceDetectorSpec>`.
|
|
@@ -324,6 +358,15 @@ namespace margelo::nitro::myazakyc::bridge::swift {
|
|
|
324
358
|
return Result<FaceResult>::withError(error);
|
|
325
359
|
}
|
|
326
360
|
|
|
361
|
+
// pragma MARK: Result<std::shared_ptr<Promise<bool>>>
|
|
362
|
+
using Result_std__shared_ptr_Promise_bool___ = Result<std::shared_ptr<Promise<bool>>>;
|
|
363
|
+
inline Result_std__shared_ptr_Promise_bool___ create_Result_std__shared_ptr_Promise_bool___(const std::shared_ptr<Promise<bool>>& value) noexcept {
|
|
364
|
+
return Result<std::shared_ptr<Promise<bool>>>::withValue(value);
|
|
365
|
+
}
|
|
366
|
+
inline Result_std__shared_ptr_Promise_bool___ create_Result_std__shared_ptr_Promise_bool___(const std::exception_ptr& error) noexcept {
|
|
367
|
+
return Result<std::shared_ptr<Promise<bool>>>::withError(error);
|
|
368
|
+
}
|
|
369
|
+
|
|
327
370
|
// pragma MARK: std::shared_ptr<HybridMyazaRectDetectorSpec>
|
|
328
371
|
/**
|
|
329
372
|
* Specialized version of `std::shared_ptr<HybridMyazaRectDetectorSpec>`.
|
|
@@ -20,6 +20,7 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
20
20
|
#include "FaceResult.hpp"
|
|
21
21
|
#include <memory>
|
|
22
22
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
23
|
+
#include <NitroModules/Promise.hpp>
|
|
23
24
|
|
|
24
25
|
#include "KycSdkReactNative-Swift-Cxx-Umbrella.hpp"
|
|
25
26
|
|
|
@@ -79,6 +80,22 @@ namespace margelo::nitro::myazakyc {
|
|
|
79
80
|
auto __value = std::move(__result.value());
|
|
80
81
|
return __value;
|
|
81
82
|
}
|
|
83
|
+
inline bool isModelReady() override {
|
|
84
|
+
auto __result = _swiftPart.isModelReady();
|
|
85
|
+
if (__result.hasError()) [[unlikely]] {
|
|
86
|
+
std::rethrow_exception(__result.error());
|
|
87
|
+
}
|
|
88
|
+
auto __value = std::move(__result.value());
|
|
89
|
+
return __value;
|
|
90
|
+
}
|
|
91
|
+
inline std::shared_ptr<Promise<bool>> prepareModel() override {
|
|
92
|
+
auto __result = _swiftPart.prepareModel();
|
|
93
|
+
if (__result.hasError()) [[unlikely]] {
|
|
94
|
+
std::rethrow_exception(__result.error());
|
|
95
|
+
}
|
|
96
|
+
auto __value = std::move(__result.value());
|
|
97
|
+
return __value;
|
|
98
|
+
}
|
|
82
99
|
|
|
83
100
|
private:
|
|
84
101
|
KycSdkReactNative::HybridMyazaFaceDetectorSpec_cxx _swiftPart;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
///
|
|
2
|
+
/// Func_void_bool.swift
|
|
3
|
+
/// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
|
|
4
|
+
/// https://github.com/mrousavy/nitro
|
|
5
|
+
/// Copyright © Marc Rousavy @ Margelo
|
|
6
|
+
///
|
|
7
|
+
|
|
8
|
+
import NitroModules
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Wraps a Swift `(_ value: Bool) -> Void` as a class.
|
|
12
|
+
* This class can be used from C++, e.g. to wrap the Swift closure as a `std::function`.
|
|
13
|
+
*/
|
|
14
|
+
public final class Func_void_bool {
|
|
15
|
+
public typealias bridge = margelo.nitro.myazakyc.bridge.swift
|
|
16
|
+
|
|
17
|
+
private let closure: (_ value: Bool) -> Void
|
|
18
|
+
|
|
19
|
+
public init(_ closure: @escaping (_ value: Bool) -> Void) {
|
|
20
|
+
self.closure = closure
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@inline(__always)
|
|
24
|
+
public func call(value: Bool) -> Void {
|
|
25
|
+
self.closure(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Casts this instance to a retained unsafe raw pointer.
|
|
30
|
+
* This acquires one additional strong reference on the object!
|
|
31
|
+
*/
|
|
32
|
+
@inline(__always)
|
|
33
|
+
public func toUnsafe() -> UnsafeMutableRawPointer {
|
|
34
|
+
return Unmanaged.passRetained(self).toOpaque()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Casts an unsafe pointer to a `Func_void_bool`.
|
|
39
|
+
* The pointer has to be a retained opaque `Unmanaged<Func_void_bool>`.
|
|
40
|
+
* This removes one strong reference from the object!
|
|
41
|
+
*/
|
|
42
|
+
@inline(__always)
|
|
43
|
+
public static func fromUnsafe(_ pointer: UnsafeMutableRawPointer) -> Func_void_bool {
|
|
44
|
+
return Unmanaged<Func_void_bool>.fromOpaque(pointer).takeRetainedValue()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -15,6 +15,8 @@ public protocol HybridMyazaFaceDetectorSpec_protocol: HybridObject {
|
|
|
15
15
|
|
|
16
16
|
// Methods
|
|
17
17
|
func detectFace(frame: (any HybridFrameSpec)) throws -> FaceResult
|
|
18
|
+
func isModelReady() throws -> Bool
|
|
19
|
+
func prepareModel() throws -> Promise<Bool>
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
public extension HybridMyazaFaceDetectorSpec_protocol {
|
|
@@ -140,4 +140,35 @@ open class HybridMyazaFaceDetectorSpec_cxx {
|
|
|
140
140
|
return bridge.create_Result_FaceResult_(__exceptionPtr)
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
@inline(__always)
|
|
145
|
+
public final func isModelReady() -> bridge.Result_bool_ {
|
|
146
|
+
do {
|
|
147
|
+
let __result = try self.__implementation.isModelReady()
|
|
148
|
+
let __resultCpp = __result
|
|
149
|
+
return bridge.create_Result_bool_(__resultCpp)
|
|
150
|
+
} catch (let __error) {
|
|
151
|
+
let __exceptionPtr = __error.toCpp()
|
|
152
|
+
return bridge.create_Result_bool_(__exceptionPtr)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@inline(__always)
|
|
157
|
+
public final func prepareModel() -> bridge.Result_std__shared_ptr_Promise_bool___ {
|
|
158
|
+
do {
|
|
159
|
+
let __result = try self.__implementation.prepareModel()
|
|
160
|
+
let __resultCpp = { () -> bridge.std__shared_ptr_Promise_bool__ in
|
|
161
|
+
let __promise = bridge.create_std__shared_ptr_Promise_bool__()
|
|
162
|
+
let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_bool__(__promise)
|
|
163
|
+
__result
|
|
164
|
+
.then({ __result in __promiseHolder.resolve(__result) })
|
|
165
|
+
.catch({ __error in __promiseHolder.reject(__error.toCpp()) })
|
|
166
|
+
return __promise
|
|
167
|
+
}()
|
|
168
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__resultCpp)
|
|
169
|
+
} catch (let __error) {
|
|
170
|
+
let __exceptionPtr = __error.toCpp()
|
|
171
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__exceptionPtr)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
143
174
|
}
|
|
@@ -15,6 +15,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
15
15
|
// load custom methods/properties
|
|
16
16
|
registerHybrids(this, [](Prototype& prototype) {
|
|
17
17
|
prototype.registerHybridMethod("detectFace", &HybridMyazaFaceDetectorSpec::detectFace);
|
|
18
|
+
prototype.registerHybridMethod("isModelReady", &HybridMyazaFaceDetectorSpec::isModelReady);
|
|
19
|
+
prototype.registerHybridMethod("prepareModel", &HybridMyazaFaceDetectorSpec::prepareModel);
|
|
18
20
|
});
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -21,6 +21,7 @@ namespace margelo::nitro::camera { class HybridFrameSpec; }
|
|
|
21
21
|
#include "FaceResult.hpp"
|
|
22
22
|
#include <memory>
|
|
23
23
|
#include <VisionCamera/HybridFrameSpec.hpp>
|
|
24
|
+
#include <NitroModules/Promise.hpp>
|
|
24
25
|
|
|
25
26
|
namespace margelo::nitro::myazakyc {
|
|
26
27
|
|
|
@@ -54,6 +55,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
54
55
|
public:
|
|
55
56
|
// Methods
|
|
56
57
|
virtual FaceResult detectFace(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame) = 0;
|
|
58
|
+
virtual bool isModelReady() = 0;
|
|
59
|
+
virtual std::shared_ptr<Promise<bool>> prepareModel() = 0;
|
|
57
60
|
|
|
58
61
|
protected:
|
|
59
62
|
// Hybrid Setup
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myazahq/kyc-sdk-react-native",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"description": "Myaza KYC SDK for React Native (Expo) — ID verification, liveness detection, and document capture",
|
|
5
5
|
"author": "Flitstack Technologies Inc.",
|
|
6
6
|
"repository": {
|
|
@@ -61,7 +61,6 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@expo-google-fonts/karla": "^0.4.2",
|
|
63
63
|
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
|
64
|
-
"@expo/ui": "~56.0.16",
|
|
65
64
|
"@expo/vector-icons": "^14.0.4",
|
|
66
65
|
"country-flag-icons": "^1.6.17",
|
|
67
66
|
"expo-application": "~56.0.3",
|
package/src/MyazaKYC.tsx
CHANGED
|
@@ -16,6 +16,7 @@ import { KycRuntimeProvider, MyazaThemeProvider } from './components/runtime';
|
|
|
16
16
|
import { WorkflowGate } from './components/WorkflowGate';
|
|
17
17
|
import { useWorkflowMount } from './components/useWorkflowMount';
|
|
18
18
|
import { safeReportError } from './services/errors';
|
|
19
|
+
import { primeFaceModel } from './liveness/visionCameraFaceDetector';
|
|
19
20
|
import { KycFlow, type BackResult } from './components/KycFlow';
|
|
20
21
|
import { MyazaButton } from './components/MyazaButton';
|
|
21
22
|
|
|
@@ -98,6 +99,17 @@ function MyazaKYCTrigger({
|
|
|
98
99
|
const settled = state.status !== 'resolving';
|
|
99
100
|
const open = wantOpen && settled;
|
|
100
101
|
|
|
102
|
+
// Start fetching the face model the moment the user opens the flow, so the
|
|
103
|
+
// download overlaps consent and ID-type selection rather than stalling in
|
|
104
|
+
// front of the camera. Mirrors the web SDK's primeFaceMesh() on mount; the
|
|
105
|
+
// liveness step still gates on isFaceModelReady(), so this is best-effort.
|
|
106
|
+
const primedRef = useRef(false);
|
|
107
|
+
useEffect(() => {
|
|
108
|
+
if (!wantOpen || primedRef.current) return;
|
|
109
|
+
primedRef.current = true;
|
|
110
|
+
primeFaceModel();
|
|
111
|
+
}, [wantOpen]);
|
|
112
|
+
|
|
101
113
|
// Reported only once the user has actually tried to start. Prefetching must
|
|
102
114
|
// not fire a consumer's error handler for a flow they never opened.
|
|
103
115
|
const reportedRef = useRef(false);
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Image, View } from 'react-native';
|
|
3
|
+
import { SvgXml } from 'react-native-svg';
|
|
4
|
+
|
|
5
|
+
import { radius, spacing } from '../config/theme';
|
|
6
|
+
import { useTheme } from './runtime';
|
|
7
|
+
import { MyazaText } from './Typography';
|
|
8
|
+
|
|
9
|
+
/** Whether a logo URI points at an SVG (extension check, query-safe). */
|
|
10
|
+
export function isSvgUri(uri: string): boolean {
|
|
11
|
+
const path = uri.split('?')[0] ?? uri;
|
|
12
|
+
return path.toLowerCase().endsWith('.svg');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Deliberately NOT on a Liquid Glass capsule, though it would balance the
|
|
16
|
+
// theme/close capsule opposite it. Glass at capsule scale is the material for
|
|
17
|
+
// controls — something that floats and responds to touch. The brand is a label,
|
|
18
|
+
// so a pill around it promises a tap that never does anything. The header block
|
|
19
|
+
// behind it is glass, which is the correct use: a surface, not a control.
|
|
20
|
+
//
|
|
21
|
+
// Persistent header brand — circular logo avatar + company name. A broken/missing
|
|
22
|
+
// logo image collapses the whole brand bar (mirrors the web SDK's `onError` and
|
|
23
|
+
// Flutter's `errorBuilder`), so the header never shows a broken-image box.
|
|
24
|
+
export function BrandBar({
|
|
25
|
+
logoUri,
|
|
26
|
+
companyName,
|
|
27
|
+
}: {
|
|
28
|
+
logoUri: string;
|
|
29
|
+
companyName: string;
|
|
30
|
+
}): React.ReactElement | null {
|
|
31
|
+
const { colors } = useTheme();
|
|
32
|
+
const [broken, setBroken] = useState(false);
|
|
33
|
+
const svg = isSvgUri(logoUri);
|
|
34
|
+
// SVG logos render through SvgXml with SELF-FETCHED markup — the exact
|
|
35
|
+
// pipeline the country flags use, which is proven on-device. SvgUri's own
|
|
36
|
+
// fetch/sizing was flaky here (the favicon rendered at intrinsic size
|
|
37
|
+
// instead of filling the chip).
|
|
38
|
+
const [xml, setXml] = useState<string | null>(null);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (!svg) return;
|
|
41
|
+
let cancelled = false;
|
|
42
|
+
fetch(logoUri)
|
|
43
|
+
.then((r) => (r.ok ? r.text() : Promise.reject(new Error(String(r.status)))))
|
|
44
|
+
.then((text) => {
|
|
45
|
+
if (!cancelled) setXml(text);
|
|
46
|
+
})
|
|
47
|
+
.catch(() => {
|
|
48
|
+
if (!cancelled) setBroken(true);
|
|
49
|
+
});
|
|
50
|
+
return () => {
|
|
51
|
+
cancelled = true;
|
|
52
|
+
};
|
|
53
|
+
}, [logoUri, svg]);
|
|
54
|
+
if (broken) return null;
|
|
55
|
+
return (
|
|
56
|
+
<>
|
|
57
|
+
{/* TWO layers, deliberately.
|
|
58
|
+
|
|
59
|
+
A shadow and `overflow: 'hidden'` CANNOT live on the same view on iOS:
|
|
60
|
+
overflow-hidden compiles to `masksToBounds`, which clips the shadow
|
|
61
|
+
away with everything else outside the bounds. Android draws `elevation`
|
|
62
|
+
outside the view, so it survived there — which is why the chip lost its
|
|
63
|
+
ring on iPhone only.
|
|
64
|
+
|
|
65
|
+
So the outer view owns the border and shadow and does NOT clip, and the
|
|
66
|
+
inner one clips the logo. Same split Flutter gets for free by putting
|
|
67
|
+
the border on the Container's decoration and the crop in a ClipOval.
|
|
68
|
+
|
|
69
|
+
The white plate is load-bearing, not decoration: brand logos are
|
|
70
|
+
routinely dark artwork on a transparent background (the import picks up
|
|
71
|
+
favicons), which would vanish against a dark header. */}
|
|
72
|
+
<View
|
|
73
|
+
style={{
|
|
74
|
+
width: 28,
|
|
75
|
+
height: 28,
|
|
76
|
+
borderRadius: radius.full,
|
|
77
|
+
backgroundColor: '#FFFFFF',
|
|
78
|
+
// Light ring + soft shadow — the same treatment as the Flutter SDK's
|
|
79
|
+
// brand chip (black 5% ring, black 6% blur-4 y-1 shadow) and the web
|
|
80
|
+
// SDK's ring-1 ring-black/5.
|
|
81
|
+
borderWidth: 1,
|
|
82
|
+
borderColor: 'rgba(0,0,0,0.05)',
|
|
83
|
+
shadowColor: '#000000',
|
|
84
|
+
shadowOpacity: 0.06,
|
|
85
|
+
shadowRadius: 4,
|
|
86
|
+
shadowOffset: { width: 0, height: 1 },
|
|
87
|
+
elevation: 1,
|
|
88
|
+
marginRight: spacing.sm,
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
justifyContent: 'center',
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<View
|
|
94
|
+
style={{
|
|
95
|
+
// Fills the parent's CONTENT box (inside its 1px border) and is the
|
|
96
|
+
// only thing that clips, so the logo reaches the ring on every side.
|
|
97
|
+
alignSelf: 'stretch',
|
|
98
|
+
flex: 1,
|
|
99
|
+
borderRadius: radius.full,
|
|
100
|
+
overflow: 'hidden',
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
{/* Fill + cover-crop, matching Flutter's SizedBox.expand + BoxFit.cover.
|
|
104
|
+
|
|
105
|
+
Sized in PERCENTAGES, not the literal 26 this used to hardcode: that
|
|
106
|
+
number was "28 minus the 1px border on each side", so it silently
|
|
107
|
+
went wrong the moment either value changed, and it left the logo a
|
|
108
|
+
pixel short of the ring. The clipping parent now owns the geometry.
|
|
109
|
+
|
|
110
|
+
SVG logos are common here because the brand import picks up site
|
|
111
|
+
favicons, and <Image> cannot decode SVG at all — hence the fetched
|
|
112
|
+
markup through SvgXml. `slice` is the SVG spelling of cover-crop. */}
|
|
113
|
+
{svg ? (
|
|
114
|
+
xml ? (
|
|
115
|
+
<SvgXml xml={xml} width="100%" height="100%" preserveAspectRatio="xMidYMid slice" />
|
|
116
|
+
) : null
|
|
117
|
+
) : (
|
|
118
|
+
<Image
|
|
119
|
+
source={{ uri: logoUri }}
|
|
120
|
+
style={{ width: '100%', height: '100%' }}
|
|
121
|
+
resizeMode="cover"
|
|
122
|
+
onError={() => setBroken(true)}
|
|
123
|
+
/>
|
|
124
|
+
)}
|
|
125
|
+
</View>
|
|
126
|
+
</View>
|
|
127
|
+
<MyazaText
|
|
128
|
+
variant="heading3"
|
|
129
|
+
numberOfLines={1}
|
|
130
|
+
color={colors.textDark}
|
|
131
|
+
style={{ flexShrink: 1, fontSize: 14 }}
|
|
132
|
+
>
|
|
133
|
+
{companyName}
|
|
134
|
+
</MyazaText>
|
|
135
|
+
</>
|
|
136
|
+
);
|
|
137
|
+
}
|