@myazahq/kyc-sdk-react-native 2.5.0 → 3.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.
- package/README.md +277 -7
- package/android/build.gradle +30 -16
- package/android/consumer-rules.pro +22 -0
- package/android/src/main/AndroidManifest.xml +17 -6
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +27 -70
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +14 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/MlKitModelReadiness.kt +154 -0
- package/app.plugin.js +146 -10
- package/ios/HybridMyazaFaceDetector.swift +8 -3
- package/ios/HybridMyazaTextRecognizer.swift +8 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +21 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +8 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +16 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +2 -0
- package/package.json +30 -9
- package/src/MyazaKYC.tsx +4 -11
- package/src/assets/fonts/Karla_400Regular.ttf +0 -0
- package/src/assets/fonts/Karla_500Medium.ttf +0 -0
- package/src/assets/fonts/Karla_600SemiBold.ttf +0 -0
- package/src/assets/fonts/Karla_700Bold.ttf +0 -0
- package/src/assets/fonts/OFL-Karla.txt +93 -0
- package/src/assets/fonts/OFL-SpaceGrotesk.txt +93 -0
- package/src/assets/fonts/README.md +12 -0
- package/src/assets/fonts/SpaceGrotesk_500Medium.ttf +0 -0
- package/src/assets/fonts/SpaceGrotesk_600SemiBold.ttf +0 -0
- package/src/assets/fonts/SpaceGrotesk_700Bold.ttf +0 -0
- package/src/capture/useAutoCapture.ts +8 -1
- package/src/components/CountryField.tsx +8 -0
- package/src/components/CountryRegionPicker.tsx +184 -0
- package/src/components/DialCodePicker.tsx +47 -68
- package/src/components/DialCodeRow.tsx +102 -0
- package/src/components/DocumentCropper.tsx +30 -24
- package/src/components/DocumentReview.tsx +11 -1
- package/src/components/DocumentReviewSide.tsx +6 -2
- package/src/components/DocumentReviewZoom.tsx +5 -1
- package/src/components/FramedMapPicker.tsx +122 -0
- package/src/components/GeoBadge.tsx +34 -0
- package/src/components/Icon.tsx +10 -192
- package/src/components/KycFlow.tsx +69 -34
- package/src/components/LineSkeleton.tsx +100 -0
- package/src/components/MapChrome.tsx +67 -0
- package/src/components/MapPinMarker.tsx +54 -0
- package/src/components/MapPinPicker.tsx +179 -0
- package/src/components/MilestoneTrack.tsx +155 -0
- package/src/components/MyazaButton.tsx +9 -0
- package/src/components/MyazaInput.tsx +17 -1
- package/src/components/OptionRow.tsx +22 -3
- package/src/components/PhoneNumberInput.tsx +8 -0
- package/src/components/PoweredBy.tsx +5 -5
- package/src/components/PresenceBlocks.tsx +153 -0
- package/src/components/RequiredDocumentPill.tsx +114 -0
- package/src/components/StepHeader.tsx +23 -3
- package/src/components/StepView.tsx +17 -0
- package/src/components/StickyActions.tsx +56 -0
- package/src/components/dialCodeRows.ts +64 -0
- package/src/components/documentReviewCopy.ts +39 -0
- package/src/components/fonts.ts +14 -17
- package/src/components/icon-map.ts +176 -0
- package/src/components/stepHeaderMeta.tsx +48 -5
- package/src/config/addressCollection.ts +125 -0
- package/src/config/biometricOptions.ts +100 -0
- package/src/config/business.ts +20 -1
- package/src/config/businessSteps.ts +8 -2
- package/src/config/consentStep.ts +19 -0
- package/src/config/documentCaptureMethods.ts +31 -0
- package/src/config/proofOfAddress.ts +54 -11
- package/src/config/regions.ts +24 -0
- package/src/config/stepOrder.ts +79 -5
- package/src/config/uploadLimits.ts +35 -0
- package/src/config/workflowMerge.ts +13 -5
- package/src/index.ts +49 -1
- package/src/lib/address-current-location.ts +171 -0
- package/src/lib/address-field-modes.ts +119 -0
- package/src/lib/address-flow.ts +172 -0
- package/src/lib/address-helpers.ts +48 -0
- package/src/lib/address-line.ts +76 -0
- package/src/lib/address-pin-move.ts +97 -0
- package/src/lib/address-step-recovery.ts +63 -0
- package/src/lib/authed-image.ts +78 -0
- package/src/lib/biometric-copy.ts +51 -0
- package/src/lib/captureRing.ts +83 -0
- package/src/lib/country-adoption.ts +89 -0
- package/src/lib/documentCaptureCheck.ts +136 -0
- package/src/lib/inferred-country.ts +65 -0
- package/src/lib/livenessLayout.ts +49 -0
- package/src/lib/map-frame.ts +159 -0
- package/src/lib/map-tiles.ts +143 -0
- package/src/lib/model-ready.ts +82 -0
- package/src/lib/poa-country-gate.ts +27 -0
- package/src/lib/prime-models.ts +50 -0
- package/src/lib/resubmit.ts +36 -7
- package/src/lib/result-copy.ts +116 -0
- package/src/lib/result-wait.ts +53 -0
- package/src/lib/review-map-surface.ts +26 -0
- package/src/lib/scope.ts +31 -0
- package/src/lib/selfie-sharpness.ts +211 -0
- package/src/lib/selfie-upload-wait.ts +79 -0
- package/src/lib/street-view-fov.ts +42 -0
- package/src/lib/webview-available.ts +47 -0
- package/src/liveness/avatarSource.ts +59 -0
- package/src/liveness/useLiveness.ts +6 -1
- package/src/liveness/useModelReady.ts +9 -55
- package/src/mrz/extract.ts +71 -4
- package/src/mrz/textRecognizer.ts +36 -0
- package/src/mrz/useTextModelReady.ts +21 -0
- package/src/presence/background-math.ts +100 -0
- package/src/presence/background-store.ts +82 -0
- package/src/presence/background.ts +165 -0
- package/src/presence/foreground-service.ts +193 -0
- package/src/presence/fs.ts +51 -0
- package/src/presence/geofence.ts +41 -0
- package/src/presence/math.ts +44 -0
- package/src/presence/post.ts +40 -0
- package/src/presence/report.ts +87 -0
- package/src/presence/sampler.ts +110 -0
- package/src/presence/status.ts +92 -0
- package/src/presence/store.ts +94 -0
- package/src/presence/tier.ts +36 -0
- package/src/presence/watch-wait.ts +81 -0
- package/src/screens/AddressCountryControl.tsx +115 -0
- package/src/screens/BusinessDetailsFields.tsx +3 -0
- package/src/screens/BusinessDetailsStep.tsx +2 -0
- package/src/screens/BusinessDocumentSlot.tsx +3 -2
- package/src/screens/BusinessDocumentsStep.tsx +4 -55
- package/src/screens/CompanyInfoFields.tsx +6 -1
- package/src/screens/ConsentStep.tsx +6 -17
- package/src/screens/ContactActions.tsx +54 -0
- package/src/screens/ContactDestinationField.tsx +4 -0
- package/src/screens/ContactEntryPanel.tsx +75 -0
- package/src/screens/ContactVerificationStep.tsx +26 -56
- package/src/screens/CountrySelectStep.tsx +13 -147
- package/src/screens/DocumentCaptureStep.tsx +177 -93
- package/src/screens/LivenessAvatar.tsx +33 -16
- package/src/screens/LivenessStep.tsx +188 -49
- package/src/screens/MrzScanView.tsx +33 -0
- package/src/screens/PoaDocumentTypeList.tsx +66 -0
- package/src/screens/ProofOfAddressParts.tsx +151 -0
- package/src/screens/ProofOfAddressStep.tsx +58 -99
- package/src/screens/SubmittedBadge.tsx +25 -0
- package/src/screens/SubmittedError.tsx +64 -0
- package/src/screens/SubmittedResult.tsx +117 -0
- package/src/screens/SubmittedStep.tsx +71 -164
- package/src/screens/SubmittedSuccess.tsx +127 -0
- package/src/screens/SubmittedWaiting.tsx +45 -0
- package/src/screens/address/AddressEntranceStep.tsx +161 -0
- package/src/screens/address/AddressIntroGate.tsx +148 -0
- package/src/screens/address/AddressMapStub.tsx +50 -0
- package/src/screens/address/AddressPinStep.tsx +178 -0
- package/src/screens/address/AddressReviewStep.tsx +162 -0
- package/src/screens/address/AddressSandboxTabs.tsx +158 -0
- package/src/screens/address/AddressSearchField.tsx +115 -0
- package/src/screens/address/AddressSearchStep.tsx +94 -0
- package/src/screens/address/CurrentLocationRow.tsx +135 -0
- package/src/screens/address/DetailsSheet.tsx +175 -0
- package/src/screens/address/DetailsSheetFields.tsx +184 -0
- package/src/screens/address/EntranceDropzone.tsx +178 -0
- package/src/screens/address/EntranceFraming.tsx +66 -0
- package/src/screens/address/EntrancePills.tsx +60 -0
- package/src/screens/address/FramedStreetView.tsx +155 -0
- package/src/screens/address/IntroDisclosures.tsx +210 -0
- package/src/screens/address/LabelDecisionRow.tsx +113 -0
- package/src/screens/address/PinSummaryRow.tsx +105 -0
- package/src/screens/address/ReviewAddressBand.tsx +111 -0
- package/src/screens/address/ReviewEntranceThumbs.tsx +64 -0
- package/src/screens/address/ReviewMapPicture.tsx +106 -0
- package/src/screens/address/SearchResults.tsx +105 -0
- package/src/screens/address/SearchScreen.tsx +176 -0
- package/src/screens/address/SkipForNow.tsx +43 -0
- package/src/screens/address/StreetViewChrome.tsx +81 -0
- package/src/screens/address/detail-values.ts +22 -0
- package/src/screens/address/fix-source.ts +27 -0
- package/src/screens/address/index.ts +8 -0
- package/src/screens/address/meta.ts +44 -0
- package/src/screens/address/use-address-flow.ts +200 -0
- package/src/screens/address/use-label-pin.ts +80 -0
- package/src/screens/address/use-pin-actions.ts +192 -0
- package/src/screens/consent/model.ts +79 -10
- package/src/screens/document/CaptureCheckNotice.tsx +77 -0
- package/src/screens/document/UploadPhase.tsx +177 -0
- package/src/screens/document/useDocumentCamera.ts +109 -0
- package/src/screens/liveness/CaptureRing.tsx +91 -0
- package/src/screens/liveness/LivenessCamera.tsx +63 -0
- package/src/screens/liveness/LivenessHandover.tsx +43 -0
- package/src/screens/liveness/LivenessOutcome.tsx +8 -4
- package/src/screens/liveness/SelfiePreview.tsx +31 -7
- package/src/screens/liveness/ShutterFlash.tsx +25 -0
- package/src/screens/liveness/index.ts +4 -0
- package/src/screens/liveness/useSelfieUpload.ts +23 -3
- package/src/screens/nfc/NfcScanIllustration.tsx +7 -18
- package/src/screens/useAddressPhotoAttach.ts +77 -0
- package/src/screens/useBusinessDocumentAttach.ts +93 -0
- package/src/screens/usePoaAttach.ts +12 -15
- package/src/services/api-types.ts +119 -1
- package/src/services/api-verify-types.ts +37 -0
- package/src/services/api.ts +149 -3
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/location.ts +234 -0
- package/src/services/mediaCompress.ts +23 -5
- package/src/services/uploadErrors.ts +30 -0
- package/src/services/workflowGate.ts +7 -0
- package/src/specs/MyazaTextRecognizer.nitro.ts +29 -0
- package/src/store/address.ts +97 -0
- package/src/store/derive.ts +29 -4
- package/src/store/kycStore.ts +120 -5
- package/src/store/serverConfig.ts +18 -0
- package/src/store/session.ts +23 -4
- package/src/store/state.ts +134 -0
- package/src/store/submit.ts +18 -1
- package/src/types/config.ts +63 -1
- package/src/types/verification.ts +12 -0
- package/src/types/workflow.ts +97 -1
- package/src/assets/liveness/Blink.gif +0 -0
- package/src/assets/liveness/Nod.gif +0 -0
- package/src/assets/liveness/Smile.gif +0 -0
- package/src/assets/liveness/Turn.gif +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
package com.margelo.nitro.myazakyc
|
|
2
|
+
|
|
3
|
+
import co.myazahq.kyc.rn.BuildConfig
|
|
4
|
+
import com.google.android.gms.common.api.OptionalModuleApi
|
|
5
|
+
import com.google.android.gms.common.moduleinstall.InstallStatusListener
|
|
6
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstall
|
|
7
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstallClient
|
|
8
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstallRequest
|
|
9
|
+
import com.google.android.gms.common.moduleinstall.ModuleInstallStatusUpdate
|
|
10
|
+
import com.margelo.nitro.NitroModules
|
|
11
|
+
import java.util.concurrent.CountDownLatch
|
|
12
|
+
import java.util.concurrent.TimeUnit
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The readiness gate both ML Kit detectors share.
|
|
16
|
+
*
|
|
17
|
+
* The default build fetches ML Kit's models through Play Services instead of
|
|
18
|
+
* bundling them (android/build.gradle), which is what keeps ~18.5 MB per device
|
|
19
|
+
* out of the APK. The cost is a window where the detector cannot run: first
|
|
20
|
+
* launch before the download completes, or a device with no GMS at all.
|
|
21
|
+
*
|
|
22
|
+
* Neither detector can report that through its own result. A face detector
|
|
23
|
+
* answers in `FaceResult`, where a missing model and an empty frame are both
|
|
24
|
+
* `faceCount: 0`; a text recogniser answers in `TextResult`, where both are an
|
|
25
|
+
* empty `lines` array. So a user on a fresh install would watch "position your
|
|
26
|
+
* face" forever, or aim at a passport that never auto-captures, with nothing to
|
|
27
|
+
* explain it. Hence a separate, explicit contract, checked before the camera
|
|
28
|
+
* opens rather than inferred per frame.
|
|
29
|
+
*
|
|
30
|
+
* Two properties make it actually settle, and both were missing once:
|
|
31
|
+
* - The install is URGENT (`installModules`). `deferredInstall` leaves the
|
|
32
|
+
* timing to Play Services, which may wait for an idle, charging phone; right
|
|
33
|
+
* for a model somebody might want next week, wrong for one a person is
|
|
34
|
+
* waiting on now.
|
|
35
|
+
* - The answer is REFRESHED. `isModelReady` is a synchronous Nitro call and
|
|
36
|
+
* `areModulesAvailable` is asynchronous, so it returns the cached flag, but
|
|
37
|
+
* while the flag is false each call also starts a background re-check. The
|
|
38
|
+
* cache used to be set once at flow start and never again, so a download that
|
|
39
|
+
* finished a few seconds later was never noticed and the step gave up.
|
|
40
|
+
*/
|
|
41
|
+
class MlKitModelReadiness(private val api: OptionalModuleApi) {
|
|
42
|
+
// A bundled build has the model in the APK, so there is nothing to wait for
|
|
43
|
+
// and nothing to ask. Asking anyway would be worse than useless: Play
|
|
44
|
+
// Services answers about its OWN optional modules, so it would report "not
|
|
45
|
+
// available" and strand a build that works perfectly offline — which is the
|
|
46
|
+
// exact scenario the bundled flag exists to serve.
|
|
47
|
+
@Volatile private var ready = BuildConfig.BUNDLED_ML_KIT
|
|
48
|
+
@Volatile private var checking = false
|
|
49
|
+
@Volatile private var installing = false
|
|
50
|
+
|
|
51
|
+
fun isReady(): Boolean {
|
|
52
|
+
if (!ready) refresh()
|
|
53
|
+
return ready
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Ask Play Services for the model, returning true once it is usable, and
|
|
58
|
+
* request an urgent install when it is not.
|
|
59
|
+
*
|
|
60
|
+
* Safe to call repeatedly; returns immediately when already ready. Blocking
|
|
61
|
+
* — callers run it inside a Nitro `Promise.async`.
|
|
62
|
+
*/
|
|
63
|
+
fun prepare(): Boolean {
|
|
64
|
+
if (ready) return true
|
|
65
|
+
|
|
66
|
+
val ctx = NitroModules.applicationContext
|
|
67
|
+
?: return false // No context — cannot ask Play Services anything.
|
|
68
|
+
|
|
69
|
+
val latch = CountDownLatch(1)
|
|
70
|
+
try {
|
|
71
|
+
val client = ModuleInstall.getClient(ctx)
|
|
72
|
+
client.areModulesAvailable(api)
|
|
73
|
+
.addOnSuccessListener { response ->
|
|
74
|
+
if (response.areModulesAvailable()) ready = true else requestInstall(client)
|
|
75
|
+
latch.countDown()
|
|
76
|
+
}
|
|
77
|
+
.addOnFailureListener {
|
|
78
|
+
// Thrown on devices without Google Play Services at all (Huawei, bare
|
|
79
|
+
// AOSP). Not an error to retry — it will never succeed on this device.
|
|
80
|
+
// Such orgs should build with `myazaKycBundledMlKit = true`.
|
|
81
|
+
latch.countDown()
|
|
82
|
+
}
|
|
83
|
+
latch.await(MODEL_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
|
|
84
|
+
} catch (_: Throwable) {
|
|
85
|
+
// Leave `ready` as it was; the shared gate's wait decides when to stop.
|
|
86
|
+
}
|
|
87
|
+
return ready
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Re-asks Play Services in the background; at most one check at a time. */
|
|
91
|
+
private fun refresh() {
|
|
92
|
+
if (checking) return
|
|
93
|
+
val ctx = NitroModules.applicationContext ?: return
|
|
94
|
+
checking = true
|
|
95
|
+
try {
|
|
96
|
+
ModuleInstall.getClient(ctx).areModulesAvailable(api)
|
|
97
|
+
.addOnSuccessListener { response -> if (response.areModulesAvailable()) ready = true }
|
|
98
|
+
.addOnCompleteListener { checking = false }
|
|
99
|
+
} catch (_: Throwable) {
|
|
100
|
+
checking = false
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private fun requestInstall(client: ModuleInstallClient) {
|
|
105
|
+
if (installing) return
|
|
106
|
+
installing = true
|
|
107
|
+
|
|
108
|
+
val listener = object : InstallStatusListener {
|
|
109
|
+
override fun onInstallStatusUpdated(update: ModuleInstallStatusUpdate) {
|
|
110
|
+
when (update.installState) {
|
|
111
|
+
ModuleInstallStatusUpdate.InstallState.STATE_COMPLETED -> {
|
|
112
|
+
ready = true
|
|
113
|
+
installing = false
|
|
114
|
+
client.unregisterListener(this)
|
|
115
|
+
}
|
|
116
|
+
ModuleInstallStatusUpdate.InstallState.STATE_FAILED,
|
|
117
|
+
ModuleInstallStatusUpdate.InstallState.STATE_CANCELED -> {
|
|
118
|
+
// Cleared so the next prepare() can ask again.
|
|
119
|
+
installing = false
|
|
120
|
+
client.unregisterListener(this)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// `installModules` takes a ModuleInstallRequest; `deferredInstall` takes the
|
|
127
|
+
// OptionalModuleApi itself. Handing a request to the wrong one is how this
|
|
128
|
+
// file once stopped compiling.
|
|
129
|
+
val request = ModuleInstallRequest.Builder().addApi(api).setListener(listener).build()
|
|
130
|
+
client.installModules(request)
|
|
131
|
+
.addOnSuccessListener { response ->
|
|
132
|
+
// The listener never fires for a module that was already there.
|
|
133
|
+
if (response.areModulesAlreadyInstalled()) {
|
|
134
|
+
ready = true
|
|
135
|
+
installing = false
|
|
136
|
+
client.unregisterListener(listener)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
.addOnFailureListener {
|
|
140
|
+
installing = false
|
|
141
|
+
client.unregisterListener(listener)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private companion object {
|
|
146
|
+
/**
|
|
147
|
+
* Bound on the availability query. It is a local Play Services call, not the
|
|
148
|
+
* model download — the download runs in the background afterwards. Generous
|
|
149
|
+
* enough for a cold Play Services process, short enough that a wedged one
|
|
150
|
+
* cannot stall flow start.
|
|
151
|
+
*/
|
|
152
|
+
const val MODEL_CHECK_TIMEOUT_MS = 3_000L
|
|
153
|
+
}
|
|
154
|
+
}
|
package/app.plugin.js
CHANGED
|
@@ -27,8 +27,12 @@ const pkg = require('./package.json');
|
|
|
27
27
|
// unconditionally would break code-signing for every consumer who does not read
|
|
28
28
|
// chips and has not enabled it.
|
|
29
29
|
//
|
|
30
|
-
// Camera preview/capture itself comes from react-native-vision-camera
|
|
31
|
-
// config plugin
|
|
30
|
+
// Camera preview/capture itself comes from react-native-vision-camera, but v5
|
|
31
|
+
// ships NO config plugin (v4 did), so it must NOT be listed in the host app's
|
|
32
|
+
// `plugins`. Expo then loads the package's main entry as a plugin and prebuild
|
|
33
|
+
// dies on `Cannot find module '.../lib/VisionCamera'`. That is why the camera
|
|
34
|
+
// permission and usage string are declared HERE. This plugin is intentionally
|
|
35
|
+
// minimal and idempotent.
|
|
32
36
|
// ---------------------------------------------------------------------------
|
|
33
37
|
|
|
34
38
|
const CAMERA_USAGE =
|
|
@@ -37,6 +41,9 @@ const CAMERA_USAGE =
|
|
|
37
41
|
const NFC_USAGE =
|
|
38
42
|
'We read the secure chip in your passport or ID card to confirm the document is genuine.';
|
|
39
43
|
|
|
44
|
+
const LOCATION_USAGE =
|
|
45
|
+
'We use your location to confirm you are at the address you pin during verification.';
|
|
46
|
+
|
|
40
47
|
/**
|
|
41
48
|
* The eMRTD application identifier.
|
|
42
49
|
*
|
|
@@ -62,6 +69,23 @@ function withIosCameraUsage(config, customMessage) {
|
|
|
62
69
|
});
|
|
63
70
|
}
|
|
64
71
|
|
|
72
|
+
/**
|
|
73
|
+
* iOS location usage string, for the address-collection step's one-shot fix
|
|
74
|
+
* ("Use my current location" + the attest-presence claim). Written by DEFAULT
|
|
75
|
+
* because its ABSENCE is a crash: iOS kills an app that requests location
|
|
76
|
+
* without a usage string, and whether the step runs is decided by the org's
|
|
77
|
+
* WORKFLOW, not by anything the host app author can see at build time. An
|
|
78
|
+
* unused usage string costs nothing. `location: false` opts out.
|
|
79
|
+
*/
|
|
80
|
+
function withIosLocationUsage(config, customMessage) {
|
|
81
|
+
return withInfoPlist(config, (cfg) => {
|
|
82
|
+
if (!cfg.modResults.NSLocationWhenInUseUsageDescription) {
|
|
83
|
+
cfg.modResults.NSLocationWhenInUseUsageDescription = customMessage || LOCATION_USAGE;
|
|
84
|
+
}
|
|
85
|
+
return cfg;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
65
89
|
const AID_KEY = 'com.apple.developer.nfc.readersession.iso7816.select-identifiers';
|
|
66
90
|
const FORMATS_KEY = 'com.apple.developer.nfc.readersession.formats';
|
|
67
91
|
|
|
@@ -112,25 +136,135 @@ function withIosNfc(config, customMessage) {
|
|
|
112
136
|
});
|
|
113
137
|
}
|
|
114
138
|
|
|
115
|
-
|
|
139
|
+
/**
|
|
140
|
+
* The Android permission list for a given set of opt-ins. Pure and exported
|
|
141
|
+
* so the tests can pin what `location: 'always'` declares — a permission
|
|
142
|
+
* that changes review posture must never arrive by accident.
|
|
143
|
+
*/
|
|
144
|
+
function androidPermissionsFor({ nfc, location, backgroundLocation }) {
|
|
145
|
+
return [
|
|
146
|
+
'android.permission.CAMERA',
|
|
147
|
+
'android.permission.INTERNET',
|
|
148
|
+
...(nfc ? ['android.permission.NFC'] : []),
|
|
149
|
+
// Foreground location, for the address-collection step's one-shot fix.
|
|
150
|
+
...(location
|
|
151
|
+
? ['android.permission.ACCESS_COARSE_LOCATION', 'android.permission.ACCESS_FINE_LOCATION']
|
|
152
|
+
: []),
|
|
153
|
+
// The always-on presence tier (OkHi model, opt-in): OS geofencing needs
|
|
154
|
+
// the background grant, and Google Play reviews any app that declares
|
|
155
|
+
// it — which is why this is `location: 'always'` only, never a default.
|
|
156
|
+
// The foreground-service pair rides the same opt-in: FOREGROUND_SERVICE
|
|
157
|
+
// is a normal permission, and FOREGROUND_SERVICE_LOCATION (API 34+) is
|
|
158
|
+
// what lets expo-location's service declare the `location` type; Play
|
|
159
|
+
// reviews it under the same location declaration.
|
|
160
|
+
...(backgroundLocation
|
|
161
|
+
? [
|
|
162
|
+
'android.permission.ACCESS_BACKGROUND_LOCATION',
|
|
163
|
+
'android.permission.FOREGROUND_SERVICE',
|
|
164
|
+
'android.permission.FOREGROUND_SERVICE_LOCATION',
|
|
165
|
+
]
|
|
166
|
+
: []),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function withAndroidPermissions(config, opts) {
|
|
171
|
+
return withAndroidManifest(config, (cfg) => {
|
|
172
|
+
AndroidConfig.Permissions.ensurePermissions(cfg.modResults, androidPermissionsFor(opts));
|
|
173
|
+
return cfg;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Declares `android.hardware.nfc` as NOT required, on the host's manifest.
|
|
179
|
+
*
|
|
180
|
+
* This is not decoration, and it must never be separated from the NFC
|
|
181
|
+
* permission. Android INFERS `<uses-feature android:name="android.hardware.nfc"
|
|
182
|
+
* android:required="true">` from `android.permission.NFC` whenever the feature
|
|
183
|
+
* is not declared explicitly — and Play then hides the app from every device
|
|
184
|
+
* without an NFC radio. For a KYC SDK that is most of the catalogue in the
|
|
185
|
+
* markets we serve.
|
|
186
|
+
*
|
|
187
|
+
* So: the permission (androidPermissionsFor) and this declaration are written
|
|
188
|
+
* under the same `nfc: true` opt-in in withMyazaKyc, and nothing else writes
|
|
189
|
+
* either. plugin.test.ts pins that pairing with a source scan, because two
|
|
190
|
+
* separate functions are otherwise free to drift apart.
|
|
191
|
+
*
|
|
192
|
+
* Pure and exported so the pairing can be pinned by a test. The failure it
|
|
193
|
+
* guards has no runtime symptom in development — the app builds, installs and
|
|
194
|
+
* runs perfectly on the NFC-capable phone the developer is holding, and is
|
|
195
|
+
* simply absent from the store listing for everyone else.
|
|
196
|
+
*
|
|
197
|
+
* @param {Record<string, any>} manifest the `modResults.manifest` object
|
|
198
|
+
*/
|
|
199
|
+
function ensureNfcFeatureOptional(manifest) {
|
|
200
|
+
const list = Array.isArray(manifest['uses-feature']) ? manifest['uses-feature'] : [];
|
|
201
|
+
const existing = list.find((f) => f && f.$ && f.$['android:name'] === 'android.hardware.nfc');
|
|
202
|
+
if (existing) {
|
|
203
|
+
// An explicit `required="true"` from the host or another library would
|
|
204
|
+
// reintroduce exactly the store-visibility problem above, so it is
|
|
205
|
+
// overwritten rather than respected.
|
|
206
|
+
existing.$['android:required'] = 'false';
|
|
207
|
+
} else {
|
|
208
|
+
list.push({
|
|
209
|
+
$: { 'android:name': 'android.hardware.nfc', 'android:required': 'false' },
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
manifest['uses-feature'] = list;
|
|
213
|
+
return manifest;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function withAndroidNfcFeature(config) {
|
|
116
217
|
return withAndroidManifest(config, (cfg) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
218
|
+
ensureNfcFeatureOptional(cfg.modResults.manifest);
|
|
219
|
+
return cfg;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** iOS side of the always-on tier: the Always usage string plus the location
|
|
224
|
+
* background mode, without which a geofence wake is refused. */
|
|
225
|
+
function withIosBackgroundLocation(config, customMessage) {
|
|
226
|
+
return withInfoPlist(config, (cfg) => {
|
|
227
|
+
if (!cfg.modResults.NSLocationAlwaysAndWhenInUseUsageDescription) {
|
|
228
|
+
cfg.modResults.NSLocationAlwaysAndWhenInUseUsageDescription =
|
|
229
|
+
customMessage || LOCATION_USAGE;
|
|
230
|
+
}
|
|
231
|
+
const modes = Array.isArray(cfg.modResults.UIBackgroundModes)
|
|
232
|
+
? cfg.modResults.UIBackgroundModes
|
|
233
|
+
: [];
|
|
234
|
+
if (!modes.includes('location')) modes.push('location');
|
|
235
|
+
cfg.modResults.UIBackgroundModes = modes;
|
|
122
236
|
return cfg;
|
|
123
237
|
});
|
|
124
238
|
}
|
|
125
239
|
|
|
126
240
|
/**
|
|
127
241
|
* @param {object} config
|
|
128
|
-
* @param {{ cameraPermission?: string, nfc?: boolean, nfcPermission?: string
|
|
242
|
+
* @param {{ cameraPermission?: string, nfc?: boolean, nfcPermission?: string,
|
|
243
|
+
* location?: boolean | 'always', locationPermission?: string }} [props]
|
|
129
244
|
*/
|
|
130
245
|
function withMyazaKyc(config, props = {}) {
|
|
246
|
+
const location = props.location !== false;
|
|
247
|
+
// `location: 'always'` opts into the background presence tier. Explicit on
|
|
248
|
+
// purpose: it changes the app's Play/App Store review posture, so it must
|
|
249
|
+
// never arrive as a side effect of a default.
|
|
250
|
+
const backgroundLocation = props.location === 'always';
|
|
131
251
|
config = withIosCameraUsage(config, props.cameraPermission);
|
|
132
|
-
config = withAndroidPermissions(config, {
|
|
252
|
+
config = withAndroidPermissions(config, {
|
|
253
|
+
nfc: props.nfc === true,
|
|
254
|
+
location,
|
|
255
|
+
backgroundLocation,
|
|
256
|
+
});
|
|
257
|
+
if (location) {
|
|
258
|
+
config = withIosLocationUsage(config, props.locationPermission);
|
|
259
|
+
}
|
|
260
|
+
if (backgroundLocation) {
|
|
261
|
+
config = withIosBackgroundLocation(config, props.locationPermission);
|
|
262
|
+
}
|
|
133
263
|
if (props.nfc === true) {
|
|
264
|
+
// Android: the permission comes from androidPermissionsFor above; this adds
|
|
265
|
+
// the uses-feature that must accompany it. Never write one without the
|
|
266
|
+
// other — see ensureNfcFeatureOptional.
|
|
267
|
+
config = withAndroidNfcFeature(config);
|
|
134
268
|
config = withIosNfc(config, props.nfcPermission);
|
|
135
269
|
}
|
|
136
270
|
return config;
|
|
@@ -141,4 +275,6 @@ module.exports = createRunOncePlugin(withMyazaKyc, pkg.name, pkg.version);
|
|
|
141
275
|
// consumers' app.json entries are unaffected.
|
|
142
276
|
module.exports.applyNfcInfoPlist = applyNfcInfoPlist;
|
|
143
277
|
module.exports.applyNfcEntitlements = applyNfcEntitlements;
|
|
278
|
+
module.exports.androidPermissionsFor = androidPermissionsFor;
|
|
279
|
+
module.exports.ensureNfcFeatureOptional = ensureNfcFeatureOptional;
|
|
144
280
|
module.exports.EMRTD_AID = EMRTD_AID;
|
|
@@ -303,8 +303,13 @@ final class HybridMyazaFaceDetector: HybridMyazaFaceDetectorSpec {
|
|
|
303
303
|
return min(max(v, 0.0), 1.0)
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
/// Smile from outer-lip width relative to the face. Input band
|
|
307
|
-
///
|
|
306
|
+
/// Smile from outer-lip width relative to the face box. Input band
|
|
307
|
+
/// (0.36→0.46): the shared detector passes above 0.5, so the pass line sits
|
|
308
|
+
/// at a lip width of 0.41 of the box. It was 0.40→0.50 (pass at 0.45), which
|
|
309
|
+
/// on device needed a full grin — a modest, natural smile never crossed it.
|
|
310
|
+
/// ML Kit on Android returns a classifier probability instead, so this band
|
|
311
|
+
/// is the iOS half of one shared threshold: tune it here, not in
|
|
312
|
+
/// detectSmile, or Android loosens with it.
|
|
308
313
|
private static func smileProbability(_ landmarks: VNFaceLandmarks2D?) -> Double {
|
|
309
314
|
guard let pts = landmarks?.outerLips?.normalizedPoints, pts.count >= 4 else {
|
|
310
315
|
return 0.0
|
|
@@ -315,7 +320,7 @@ final class HybridMyazaFaceDetector: HybridMyazaFaceDetectorSpec {
|
|
|
315
320
|
minX = min(minX, Double(p.x)); maxX = max(maxX, Double(p.x))
|
|
316
321
|
}
|
|
317
322
|
let width = maxX - minX
|
|
318
|
-
let score = (width - 0.
|
|
323
|
+
let score = (width - 0.36) / (0.46 - 0.36)
|
|
319
324
|
return min(max(score, 0.0), 1.0)
|
|
320
325
|
}
|
|
321
326
|
|
|
@@ -27,6 +27,14 @@ import NitroModules
|
|
|
27
27
|
// ---------------------------------------------------------------------------
|
|
28
28
|
|
|
29
29
|
final class HybridMyazaTextRecognizer: HybridMyazaTextRecognizerSpec {
|
|
30
|
+
// Apple Vision ships with the OS — there is no model to fetch, no Play
|
|
31
|
+
// Services equivalent, and no window in which recognition is unavailable.
|
|
32
|
+
// These exist only because Android fetches ML Kit's models on demand; iOS
|
|
33
|
+
// answers "ready" unconditionally so the shared TS flow needs no platform
|
|
34
|
+
// branch.
|
|
35
|
+
func isModelReady() throws -> Bool { true }
|
|
36
|
+
|
|
37
|
+
func prepareModel() throws -> Promise<Bool> { Promise.resolved(withResult: true) }
|
|
30
38
|
|
|
31
39
|
func recognizeText(frame: any HybridFrameSpec, bottomFraction: Double) throws -> TextResult {
|
|
32
40
|
guard
|
|
@@ -76,5 +76,26 @@ namespace margelo::nitro::myazakyc {
|
|
|
76
76
|
return __promise;
|
|
77
77
|
}();
|
|
78
78
|
}
|
|
79
|
+
bool JHybridMyazaTextRecognizerSpec::isModelReady() {
|
|
80
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jboolean()>("isModelReady");
|
|
81
|
+
auto __result = method(_javaPart);
|
|
82
|
+
return static_cast<bool>(__result);
|
|
83
|
+
}
|
|
84
|
+
std::shared_ptr<Promise<bool>> JHybridMyazaTextRecognizerSpec::prepareModel() {
|
|
85
|
+
static const auto method = _javaPart->javaClassStatic()->getMethod<jni::local_ref<JPromise::javaobject>()>("prepareModel");
|
|
86
|
+
auto __result = method(_javaPart);
|
|
87
|
+
return [&]() {
|
|
88
|
+
auto __promise = Promise<bool>::create();
|
|
89
|
+
__result->cthis()->addOnResolvedListener([=](const jni::alias_ref<jni::JObject>& __boxedResult) {
|
|
90
|
+
auto __result = jni::static_ref_cast<jni::JBoolean>(__boxedResult);
|
|
91
|
+
__promise->resolve(static_cast<bool>(__result->value()));
|
|
92
|
+
});
|
|
93
|
+
__result->cthis()->addOnRejectedListener([=](const jni::alias_ref<jni::JThrowable>& __throwable) {
|
|
94
|
+
jni::JniException __jniError(__throwable);
|
|
95
|
+
__promise->reject(std::make_exception_ptr(__jniError));
|
|
96
|
+
});
|
|
97
|
+
return __promise;
|
|
98
|
+
}();
|
|
99
|
+
}
|
|
79
100
|
|
|
80
101
|
} // namespace margelo::nitro::myazakyc
|
|
@@ -56,6 +56,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
56
56
|
// Methods
|
|
57
57
|
TextResult recognizeText(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame, double bottomFraction) override;
|
|
58
58
|
std::shared_ptr<Promise<TextResult>> recognizeTextInImage(const std::string& uri, double bottomFraction) override;
|
|
59
|
+
bool isModelReady() override;
|
|
60
|
+
std::shared_ptr<Promise<bool>> prepareModel() override;
|
|
59
61
|
|
|
60
62
|
private:
|
|
61
63
|
jni::global_ref<JHybridMyazaTextRecognizerSpec::JavaPart> _javaPart;
|
|
@@ -37,6 +37,14 @@ abstract class HybridMyazaTextRecognizerSpec: HybridObject() {
|
|
|
37
37
|
@DoNotStrip
|
|
38
38
|
@Keep
|
|
39
39
|
abstract fun recognizeTextInImage(uri: String, bottomFraction: Double): Promise<TextResult>
|
|
40
|
+
|
|
41
|
+
@DoNotStrip
|
|
42
|
+
@Keep
|
|
43
|
+
abstract fun isModelReady(): Boolean
|
|
44
|
+
|
|
45
|
+
@DoNotStrip
|
|
46
|
+
@Keep
|
|
47
|
+
abstract fun prepareModel(): Promise<Boolean>
|
|
40
48
|
|
|
41
49
|
// Default implementation of `HybridObject.toString()`
|
|
42
50
|
override fun toString(): String {
|
|
@@ -90,6 +90,22 @@ namespace margelo::nitro::myazakyc {
|
|
|
90
90
|
auto __value = std::move(__result.value());
|
|
91
91
|
return __value;
|
|
92
92
|
}
|
|
93
|
+
inline bool isModelReady() override {
|
|
94
|
+
auto __result = _swiftPart.isModelReady();
|
|
95
|
+
if (__result.hasError()) [[unlikely]] {
|
|
96
|
+
std::rethrow_exception(__result.error());
|
|
97
|
+
}
|
|
98
|
+
auto __value = std::move(__result.value());
|
|
99
|
+
return __value;
|
|
100
|
+
}
|
|
101
|
+
inline std::shared_ptr<Promise<bool>> prepareModel() override {
|
|
102
|
+
auto __result = _swiftPart.prepareModel();
|
|
103
|
+
if (__result.hasError()) [[unlikely]] {
|
|
104
|
+
std::rethrow_exception(__result.error());
|
|
105
|
+
}
|
|
106
|
+
auto __value = std::move(__result.value());
|
|
107
|
+
return __value;
|
|
108
|
+
}
|
|
93
109
|
|
|
94
110
|
private:
|
|
95
111
|
KycSdkReactNative::HybridMyazaTextRecognizerSpec_cxx _swiftPart;
|
|
@@ -16,6 +16,8 @@ public protocol HybridMyazaTextRecognizerSpec_protocol: HybridObject {
|
|
|
16
16
|
// Methods
|
|
17
17
|
func recognizeText(frame: (any HybridFrameSpec), bottomFraction: Double) throws -> TextResult
|
|
18
18
|
func recognizeTextInImage(uri: String, bottomFraction: Double) throws -> Promise<TextResult>
|
|
19
|
+
func isModelReady() throws -> Bool
|
|
20
|
+
func prepareModel() throws -> Promise<Bool>
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
public extension HybridMyazaTextRecognizerSpec_protocol {
|
|
@@ -159,4 +159,35 @@ open class HybridMyazaTextRecognizerSpec_cxx {
|
|
|
159
159
|
return bridge.create_Result_std__shared_ptr_Promise_TextResult___(__exceptionPtr)
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
|
+
|
|
163
|
+
@inline(__always)
|
|
164
|
+
public final func isModelReady() -> bridge.Result_bool_ {
|
|
165
|
+
do {
|
|
166
|
+
let __result = try self.__implementation.isModelReady()
|
|
167
|
+
let __resultCpp = __result
|
|
168
|
+
return bridge.create_Result_bool_(__resultCpp)
|
|
169
|
+
} catch (let __error) {
|
|
170
|
+
let __exceptionPtr = __error.toCpp()
|
|
171
|
+
return bridge.create_Result_bool_(__exceptionPtr)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@inline(__always)
|
|
176
|
+
public final func prepareModel() -> bridge.Result_std__shared_ptr_Promise_bool___ {
|
|
177
|
+
do {
|
|
178
|
+
let __result = try self.__implementation.prepareModel()
|
|
179
|
+
let __resultCpp = { () -> bridge.std__shared_ptr_Promise_bool__ in
|
|
180
|
+
let __promise = bridge.create_std__shared_ptr_Promise_bool__()
|
|
181
|
+
let __promiseHolder = bridge.wrap_std__shared_ptr_Promise_bool__(__promise)
|
|
182
|
+
__result
|
|
183
|
+
.then({ __result in __promiseHolder.resolve(__result) })
|
|
184
|
+
.catch({ __error in __promiseHolder.reject(__error.toCpp()) })
|
|
185
|
+
return __promise
|
|
186
|
+
}()
|
|
187
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__resultCpp)
|
|
188
|
+
} catch (let __error) {
|
|
189
|
+
let __exceptionPtr = __error.toCpp()
|
|
190
|
+
return bridge.create_Result_std__shared_ptr_Promise_bool___(__exceptionPtr)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
162
193
|
}
|
|
@@ -16,6 +16,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
16
16
|
registerHybrids(this, [](Prototype& prototype) {
|
|
17
17
|
prototype.registerHybridMethod("recognizeText", &HybridMyazaTextRecognizerSpec::recognizeText);
|
|
18
18
|
prototype.registerHybridMethod("recognizeTextInImage", &HybridMyazaTextRecognizerSpec::recognizeTextInImage);
|
|
19
|
+
prototype.registerHybridMethod("isModelReady", &HybridMyazaTextRecognizerSpec::isModelReady);
|
|
20
|
+
prototype.registerHybridMethod("prepareModel", &HybridMyazaTextRecognizerSpec::prepareModel);
|
|
19
21
|
});
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -57,6 +57,8 @@ namespace margelo::nitro::myazakyc {
|
|
|
57
57
|
// Methods
|
|
58
58
|
virtual TextResult recognizeText(const std::shared_ptr<margelo::nitro::camera::HybridFrameSpec>& frame, double bottomFraction) = 0;
|
|
59
59
|
virtual std::shared_ptr<Promise<TextResult>> recognizeTextInImage(const std::string& uri, double bottomFraction) = 0;
|
|
60
|
+
virtual bool isModelReady() = 0;
|
|
61
|
+
virtual std::shared_ptr<Promise<bool>> prepareModel() = 0;
|
|
60
62
|
|
|
61
63
|
protected:
|
|
62
64
|
// Hybrid Setup
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myazahq/kyc-sdk-react-native",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.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": {
|
|
@@ -49,6 +49,12 @@
|
|
|
49
49
|
"license": "MIT",
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"expo": ">=56",
|
|
52
|
+
"expo-application": ">=56",
|
|
53
|
+
"expo-device": ">=56",
|
|
54
|
+
"expo-document-picker": ">=56",
|
|
55
|
+
"expo-localization": ">=56",
|
|
56
|
+
"expo-task-manager": ">=14",
|
|
57
|
+
"react-native-webview": ">=13",
|
|
52
58
|
"react": ">=19",
|
|
53
59
|
"react-native": ">=0.83",
|
|
54
60
|
"react-native-nitro-image": ">=0.15",
|
|
@@ -58,27 +64,42 @@
|
|
|
58
64
|
"react-native-vision-camera-worklets": ">=5",
|
|
59
65
|
"react-native-worklets": ">=0.8"
|
|
60
66
|
},
|
|
67
|
+
"peerDependenciesMeta": {
|
|
68
|
+
"expo-application": {
|
|
69
|
+
"optional": true
|
|
70
|
+
},
|
|
71
|
+
"expo-device": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"expo-document-picker": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"expo-localization": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"expo-task-manager": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"react-native-webview": {
|
|
84
|
+
"optional": true
|
|
85
|
+
}
|
|
86
|
+
},
|
|
61
87
|
"dependencies": {
|
|
62
|
-
"@expo-google-fonts/karla": "^0.4.2",
|
|
63
|
-
"@expo-google-fonts/space-grotesk": "^0.4.1",
|
|
64
|
-
"@expo/vector-icons": "^14.0.4",
|
|
65
88
|
"country-flag-icons": "^1.6.17",
|
|
66
|
-
"expo-application": "~56.0.3",
|
|
67
89
|
"libphonenumber-js": "^1.13.8",
|
|
68
90
|
"expo-crypto": "~56.0.4",
|
|
69
91
|
"expo-clipboard": "~56.0.0",
|
|
70
|
-
"expo-device": "~56.0.4",
|
|
71
|
-
"expo-document-picker": "~56.0.4",
|
|
72
92
|
"expo-font": "~56.0.5",
|
|
73
93
|
"expo-glass-effect": "~56.0.4",
|
|
74
94
|
"expo-image-manipulator": "~56.0.17",
|
|
75
95
|
"expo-image-picker": "~56.0.16",
|
|
76
|
-
"expo-
|
|
96
|
+
"expo-location": "~56.0.24",
|
|
77
97
|
"expo-speech": "~56.0.3",
|
|
78
98
|
"lucide-react-native": "^1.17.0",
|
|
79
99
|
"react-native-compressor": "^1.19.0",
|
|
80
100
|
"react-native-vision-camera": "^5.0.11",
|
|
81
|
-
"zustand": "^5.0.2"
|
|
101
|
+
"zustand": "^5.0.2",
|
|
102
|
+
"expo-file-system": "~56.0.0"
|
|
82
103
|
},
|
|
83
104
|
"devDependencies": {
|
|
84
105
|
"@types/jest": "^29.5.12",
|
package/src/MyazaKYC.tsx
CHANGED
|
@@ -15,8 +15,8 @@ import type { KYCStep, MyazaKYCConfig, SupportedCountry } from './types/config';
|
|
|
15
15
|
import { KycRuntimeProvider, MyazaThemeProvider } from './components/runtime';
|
|
16
16
|
import { WorkflowGate } from './components/WorkflowGate';
|
|
17
17
|
import { useWorkflowMount } from './components/useWorkflowMount';
|
|
18
|
+
import { usePrimeModels } from './lib/prime-models';
|
|
18
19
|
import { safeReportError } from './services/errors';
|
|
19
|
-
import { primeFaceModel } from './liveness/visionCameraFaceDetector';
|
|
20
20
|
import { KycFlow, type BackResult } from './components/KycFlow';
|
|
21
21
|
import { MyazaButton } from './components/MyazaButton';
|
|
22
22
|
|
|
@@ -99,16 +99,7 @@ function MyazaKYCTrigger({
|
|
|
99
99
|
const settled = state.status !== 'resolving';
|
|
100
100
|
const open = wantOpen && settled;
|
|
101
101
|
|
|
102
|
-
|
|
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]);
|
|
102
|
+
usePrimeModels(wantOpen, config.apiKey, config.devUrl);
|
|
112
103
|
|
|
113
104
|
// Reported only once the user has actually tried to start. Prefetching must
|
|
114
105
|
// not fire a consumer's error handler for a flow they never opened.
|
|
@@ -229,6 +220,8 @@ export function useMyazaKYC<C extends SupportedCountry = SupportedCountry>(
|
|
|
229
220
|
configRef.current = config;
|
|
230
221
|
const backRef = useRef<(() => BackResult) | null>(null);
|
|
231
222
|
|
|
223
|
+
usePrimeModels(wantOpen, config.apiKey, config.devUrl);
|
|
224
|
+
|
|
232
225
|
// `refresh` is defined further down (it needs `wrappedConfig`), so it is
|
|
233
226
|
// reached through a ref rather than reordering the component around it.
|
|
234
227
|
const refreshRef = useRef<() => void>(() => undefined);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|