@myazahq/kyc-sdk-react-native 2.4.0 → 2.6.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 (246) hide show
  1. package/README.md +151 -5
  2. package/android/build.gradle +35 -3
  3. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +77 -0
  4. package/app.plugin.js +89 -8
  5. package/ios/HybridMyazaFaceDetector.swift +16 -3
  6. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
  7. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
  8. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
  9. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
  10. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
  11. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
  12. package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
  13. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
  14. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
  15. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
  16. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
  17. package/package.json +14 -2
  18. package/src/MyazaBiometricAuth.tsx +119 -0
  19. package/src/MyazaKYC.tsx +12 -0
  20. package/src/components/CountryField.tsx +8 -0
  21. package/src/components/CountryRegionPicker.tsx +184 -0
  22. package/src/components/DashedBorder.tsx +55 -0
  23. package/src/components/DialCodePicker.tsx +53 -67
  24. package/src/components/DialCodeRow.tsx +102 -0
  25. package/src/components/FramedMapPicker.tsx +122 -0
  26. package/src/components/GeoBadge.tsx +34 -0
  27. package/src/components/Icon.tsx +2 -176
  28. package/src/components/KycFlow.tsx +60 -32
  29. package/src/components/LineSkeleton.tsx +100 -0
  30. package/src/components/MapChrome.tsx +67 -0
  31. package/src/components/MapPinMarker.tsx +54 -0
  32. package/src/components/MapPinPicker.tsx +179 -0
  33. package/src/components/MilestoneTrack.tsx +155 -0
  34. package/src/components/MultiIdProgress.tsx +107 -0
  35. package/src/components/MyazaInput.tsx +39 -4
  36. package/src/components/MyazaSelect.tsx +2 -1
  37. package/src/components/OptionRow.tsx +22 -3
  38. package/src/components/OwnershipSlider.tsx +102 -0
  39. package/src/components/PhoneNumberInput.tsx +45 -4
  40. package/src/components/PoweredBy.tsx +5 -5
  41. package/src/components/PresenceBlocks.tsx +153 -0
  42. package/src/components/StaggerIn.tsx +63 -0
  43. package/src/components/StepView.tsx +43 -0
  44. package/src/components/StickyActions.tsx +56 -0
  45. package/src/components/dialCodeRows.ts +64 -0
  46. package/src/components/glass/FloatingSheet.tsx +73 -21
  47. package/src/components/glass/GlassSheet.tsx +14 -2
  48. package/src/components/icon-map.ts +176 -0
  49. package/src/components/stepHeaderMeta.tsx +45 -5
  50. package/src/config/addressCollection.ts +125 -0
  51. package/src/config/biometricOptions.ts +100 -0
  52. package/src/config/business.ts +26 -7
  53. package/src/config/businessDetailsValidity.ts +52 -0
  54. package/src/config/businessPrefill.ts +72 -0
  55. package/src/config/businessSteps.ts +20 -3
  56. package/src/config/consentStep.ts +19 -0
  57. package/src/config/keyPeople.ts +111 -7
  58. package/src/config/keyPeopleCorporate.ts +19 -0
  59. package/src/config/keyPeopleOwnerTypes.ts +12 -0
  60. package/src/config/keyPeopleOwners.ts +26 -0
  61. package/src/config/keyPeoplePrefill.ts +138 -0
  62. package/src/config/keyPeopleSectionDefs.ts +65 -0
  63. package/src/config/keyPeopleSections.ts +167 -0
  64. package/src/config/proofOfAddress.ts +54 -11
  65. package/src/config/regions.ts +24 -0
  66. package/src/config/registrationHint.ts +1 -1
  67. package/src/config/stepOrder.ts +100 -6
  68. package/src/config/theme.ts +8 -2
  69. package/src/config/uploadLimits.ts +35 -0
  70. package/src/config/website.ts +36 -0
  71. package/src/config/workflowMerge.ts +16 -5
  72. package/src/emrtd/activeAuth.ts +100 -0
  73. package/src/emrtd/extras.ts +76 -0
  74. package/src/emrtd/files.ts +5 -0
  75. package/src/emrtd/index.ts +1 -0
  76. package/src/emrtd/read.ts +8 -1
  77. package/src/emrtd/session.ts +41 -0
  78. package/src/emrtd/stages.ts +11 -0
  79. package/src/index.ts +54 -1
  80. package/src/lib/address-current-location.ts +171 -0
  81. package/src/lib/address-field-modes.ts +119 -0
  82. package/src/lib/address-flow.ts +172 -0
  83. package/src/lib/address-helpers.ts +48 -0
  84. package/src/lib/address-line.ts +76 -0
  85. package/src/lib/address-pin-move.ts +97 -0
  86. package/src/lib/address-step-recovery.ts +63 -0
  87. package/src/lib/authed-image.ts +78 -0
  88. package/src/lib/biometric-auth.ts +52 -0
  89. package/src/lib/biometric-copy.ts +51 -0
  90. package/src/lib/captureRing.ts +83 -0
  91. package/src/lib/contact-recovery.ts +44 -0
  92. package/src/lib/country-adoption.ts +89 -0
  93. package/src/lib/inferred-country.ts +65 -0
  94. package/src/lib/livenessLayout.ts +49 -0
  95. package/src/lib/map-frame.ts +159 -0
  96. package/src/lib/map-tiles.ts +143 -0
  97. package/src/lib/multi-id.ts +210 -0
  98. package/src/lib/poa-country-gate.ts +27 -0
  99. package/src/lib/resubmit.ts +130 -0
  100. package/src/lib/result-copy.ts +116 -0
  101. package/src/lib/result-wait.ts +53 -0
  102. package/src/lib/review-map-surface.ts +26 -0
  103. package/src/lib/scope.ts +31 -0
  104. package/src/lib/screen-corners.ts +50 -0
  105. package/src/lib/selfie-upload-wait.ts +79 -0
  106. package/src/lib/step-log.ts +24 -3
  107. package/src/lib/street-view-fov.ts +42 -0
  108. package/src/lib/use-multi-id-plan.ts +29 -0
  109. package/src/lib/webview-available.ts +47 -0
  110. package/src/liveness/useLiveness.ts +6 -1
  111. package/src/liveness/useModelReady.ts +65 -0
  112. package/src/liveness/visionCameraFaceDetector.ts +35 -0
  113. package/src/presence/background-math.ts +100 -0
  114. package/src/presence/background-store.ts +82 -0
  115. package/src/presence/background.ts +165 -0
  116. package/src/presence/foreground-service.ts +193 -0
  117. package/src/presence/fs.ts +51 -0
  118. package/src/presence/geofence.ts +41 -0
  119. package/src/presence/math.ts +44 -0
  120. package/src/presence/post.ts +40 -0
  121. package/src/presence/report.ts +87 -0
  122. package/src/presence/sampler.ts +110 -0
  123. package/src/presence/status.ts +92 -0
  124. package/src/presence/store.ts +94 -0
  125. package/src/presence/tier.ts +36 -0
  126. package/src/presence/watch-wait.ts +81 -0
  127. package/src/screens/AddressCountryControl.tsx +115 -0
  128. package/src/screens/ApplicantRoleStep.tsx +9 -5
  129. package/src/screens/BusinessCheckPanel.tsx +89 -0
  130. package/src/screens/BusinessDetailsFields.tsx +112 -0
  131. package/src/screens/BusinessDetailsStep.tsx +133 -145
  132. package/src/screens/BusinessDocumentSlot.tsx +5 -5
  133. package/src/screens/BusinessDocumentsStep.tsx +5 -56
  134. package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
  135. package/src/screens/BusinessPickedCard.tsx +86 -0
  136. package/src/screens/BusinessPickedSection.tsx +51 -0
  137. package/src/screens/BusinessRegistryPickers.tsx +67 -0
  138. package/src/screens/BusinessSandboxToggle.tsx +114 -0
  139. package/src/screens/BusinessSearch.tsx +199 -0
  140. package/src/screens/BusinessSearchResults.tsx +115 -0
  141. package/src/screens/CompanyInfoFields.tsx +91 -51
  142. package/src/screens/ConsentStep.tsx +37 -17
  143. package/src/screens/ContactActions.tsx +54 -0
  144. package/src/screens/ContactDestinationField.tsx +5 -0
  145. package/src/screens/ContactEntryPanel.tsx +75 -0
  146. package/src/screens/ContactVerificationStep.tsx +43 -42
  147. package/src/screens/CountrySelectStep.tsx +13 -147
  148. package/src/screens/IdTypeStep.tsx +12 -5
  149. package/src/screens/KeepLinksSheet.tsx +88 -0
  150. package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
  151. package/src/screens/KeyPeopleAwaitList.tsx +126 -0
  152. package/src/screens/KeyPeoplePending.tsx +154 -0
  153. package/src/screens/KeyPeopleSection.tsx +162 -0
  154. package/src/screens/KeyPeopleSectionsList.tsx +103 -0
  155. package/src/screens/KeyPeopleUboExemption.tsx +72 -0
  156. package/src/screens/KeyPersonCard.tsx +36 -4
  157. package/src/screens/KeyPersonForm.tsx +195 -36
  158. package/src/screens/KeyPersonKindToggle.tsx +68 -0
  159. package/src/screens/KeyPersonOwners.tsx +109 -0
  160. package/src/screens/KeyPersonRoleChips.tsx +68 -0
  161. package/src/screens/KeyPersonSheet.tsx +92 -108
  162. package/src/screens/LivenessAvatar.tsx +15 -3
  163. package/src/screens/LivenessStep.tsx +172 -52
  164. package/src/screens/NfcStep.tsx +14 -0
  165. package/src/screens/PoaDocumentTypeList.tsx +66 -0
  166. package/src/screens/ProofOfAddressParts.tsx +151 -0
  167. package/src/screens/ProofOfAddressStep.tsx +58 -96
  168. package/src/screens/SubmittedBadge.tsx +25 -0
  169. package/src/screens/SubmittedError.tsx +64 -0
  170. package/src/screens/SubmittedResult.tsx +117 -0
  171. package/src/screens/SubmittedStep.tsx +82 -119
  172. package/src/screens/SubmittedSuccess.tsx +127 -0
  173. package/src/screens/SubmittedWaiting.tsx +45 -0
  174. package/src/screens/address/AddressEntranceStep.tsx +161 -0
  175. package/src/screens/address/AddressIntroGate.tsx +148 -0
  176. package/src/screens/address/AddressMapStub.tsx +50 -0
  177. package/src/screens/address/AddressPinStep.tsx +178 -0
  178. package/src/screens/address/AddressReviewStep.tsx +162 -0
  179. package/src/screens/address/AddressSandboxTabs.tsx +158 -0
  180. package/src/screens/address/AddressSearchField.tsx +115 -0
  181. package/src/screens/address/AddressSearchStep.tsx +94 -0
  182. package/src/screens/address/CurrentLocationRow.tsx +135 -0
  183. package/src/screens/address/DetailsSheet.tsx +175 -0
  184. package/src/screens/address/DetailsSheetFields.tsx +184 -0
  185. package/src/screens/address/EntranceDropzone.tsx +178 -0
  186. package/src/screens/address/EntranceFraming.tsx +66 -0
  187. package/src/screens/address/EntrancePills.tsx +60 -0
  188. package/src/screens/address/FramedStreetView.tsx +155 -0
  189. package/src/screens/address/IntroDisclosures.tsx +210 -0
  190. package/src/screens/address/LabelDecisionRow.tsx +113 -0
  191. package/src/screens/address/PinSummaryRow.tsx +105 -0
  192. package/src/screens/address/ReviewAddressBand.tsx +111 -0
  193. package/src/screens/address/ReviewEntranceThumbs.tsx +64 -0
  194. package/src/screens/address/ReviewMapPicture.tsx +106 -0
  195. package/src/screens/address/SearchResults.tsx +105 -0
  196. package/src/screens/address/SearchScreen.tsx +176 -0
  197. package/src/screens/address/SkipForNow.tsx +43 -0
  198. package/src/screens/address/StreetViewChrome.tsx +81 -0
  199. package/src/screens/address/detail-values.ts +22 -0
  200. package/src/screens/address/fix-source.ts +27 -0
  201. package/src/screens/address/index.ts +8 -0
  202. package/src/screens/address/meta.ts +44 -0
  203. package/src/screens/address/use-address-flow.ts +200 -0
  204. package/src/screens/address/use-label-pin.ts +80 -0
  205. package/src/screens/address/use-pin-actions.ts +192 -0
  206. package/src/screens/biometric/BiometricAuthFlow.tsx +170 -0
  207. package/src/screens/consent/model.ts +79 -10
  208. package/src/screens/liveness/CaptureRing.tsx +91 -0
  209. package/src/screens/liveness/LivenessCamera.tsx +63 -0
  210. package/src/screens/liveness/LivenessHandover.tsx +43 -0
  211. package/src/screens/liveness/LivenessOutcome.tsx +4 -3
  212. package/src/screens/liveness/SelfiePreview.tsx +13 -4
  213. package/src/screens/liveness/ShutterFlash.tsx +25 -0
  214. package/src/screens/liveness/index.ts +4 -0
  215. package/src/screens/liveness/useSelfieUpload.ts +23 -3
  216. package/src/screens/nfc/NfcReadProgress.tsx +1 -0
  217. package/src/screens/nfc/NfcScanIllustration.tsx +7 -18
  218. package/src/screens/useAddressPhotoAttach.ts +77 -0
  219. package/src/screens/useAwaitingPeople.ts +90 -0
  220. package/src/screens/useBusinessDocumentAttach.ts +93 -0
  221. package/src/screens/usePoaAttach.ts +12 -15
  222. package/src/services/api-biometric.ts +37 -0
  223. package/src/services/api-types-biometric.ts +41 -0
  224. package/src/services/api-types.ts +251 -3
  225. package/src/services/api-verify-types.ts +90 -1
  226. package/src/services/api.ts +237 -3
  227. package/src/services/deviceMetadata.ts +1 -1
  228. package/src/services/errors.ts +10 -2
  229. package/src/services/location.ts +234 -0
  230. package/src/services/uploadErrors.ts +30 -0
  231. package/src/services/workflowGate.ts +8 -0
  232. package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
  233. package/src/store/address.ts +97 -0
  234. package/src/store/businessCheck.ts +113 -0
  235. package/src/store/derive.ts +44 -4
  236. package/src/store/kycStore.ts +352 -16
  237. package/src/store/serverConfig.ts +20 -0
  238. package/src/store/session.ts +227 -0
  239. package/src/store/state.ts +289 -2
  240. package/src/store/submit.ts +80 -13
  241. package/src/store/submitApplicant.ts +5 -11
  242. package/src/types/business.ts +23 -2
  243. package/src/types/config.ts +70 -2
  244. package/src/types/verification.ts +13 -1
  245. package/src/types/workflow.ts +112 -1
  246. package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
package/README.md CHANGED
@@ -56,8 +56,11 @@ npx expo install @myazahq/kyc-sdk-react-native \
56
56
  react-native-safe-area-context react-native-svg
57
57
  ```
58
58
 
59
- Add the config plugins to `app.json`. The SDK's plugin adds the iOS camera usage
60
- string + Android `CAMERA`/`INTERNET` permissions; the VisionCamera plugin wires up
59
+ Add the config plugins to `app.json`. The SDK's plugin adds the iOS camera and
60
+ location usage strings + Android `CAMERA`/`INTERNET` and foreground location
61
+ permissions (location backs the address-collection step's "Use my current
62
+ location" and presence attestation; `["@myazahq/kyc-sdk-react-native",
63
+ { "location": false }]` opts out); the VisionCamera plugin wires up
61
64
  the camera + frame processors. Make sure the **New Architecture** is on (it is by
62
65
  default on Expo SDK 56):
63
66
 
@@ -82,8 +85,9 @@ npx expo run:ios # iOS
82
85
  JAVA_HOME=/path/to/jdk-17 npx expo run:android # Android — needs JDK 17
83
86
  ```
84
87
 
85
- > The SDK plugin accepts an optional custom camera prompt:
86
- > `["@myazahq/kyc-sdk-react-native", { "cameraPermission": "Your message…" }]`.
88
+ > The SDK plugin accepts optional custom prompts:
89
+ > `["@myazahq/kyc-sdk-react-native", { "cameraPermission": "Your message…",
90
+ > "locationPermission": "Your message…" }]`.
87
91
 
88
92
  ### Bare React Native app (no Expo prebuild)
89
93
 
@@ -100,7 +104,8 @@ npm install @myazahq/kyc-sdk-react-native \
100
104
  react-native-worklets react-native-nitro-modules react-native-nitro-image \
101
105
  react-native-safe-area-context react-native-svg \
102
106
  expo expo-image-manipulator expo-image-picker expo-speech expo-font \
103
- expo-glass-effect expo-application expo-crypto expo-device expo-localization
107
+ expo-glass-effect expo-application expo-crypto expo-device expo-localization \
108
+ expo-location
104
109
 
105
110
  # 3. iOS pods:
106
111
  cd ios && pod install && cd ..
@@ -113,11 +118,15 @@ prebuild):
113
118
  ```xml
114
119
  <key>NSCameraUsageDescription</key>
115
120
  <string>We use the camera to photograph your ID and capture a live selfie.</string>
121
+ <key>NSLocationWhenInUseUsageDescription</key>
122
+ <string>We use your location to confirm you are at the address you pin during verification.</string>
116
123
  ```
117
124
  - **Android** — add to `android/app/src/main/AndroidManifest.xml`:
118
125
  ```xml
119
126
  <uses-permission android:name="android.permission.CAMERA" />
120
127
  <uses-permission android:name="android.permission.INTERNET" />
128
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
129
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
121
130
  ```
122
131
  - Ensure the **New Architecture** is enabled (`newArchEnabled=true` in
123
132
  `android/gradle.properties`; `RCT_NEW_ARCH_ENABLED=1` for the iOS Podfile install),
@@ -560,3 +569,140 @@ Full documentation, configuration options, and webhook setup: **[trust.myaza.co/
560
569
  ## License
561
570
 
562
571
  MIT © Flitstack Technologies Inc.
572
+
573
+ ## Presence reporting (Address Intelligence)
574
+
575
+ When a workflow enables presence verification (`addressCollection.presence.enabled`),
576
+ the SDK stores the confirmed pin on-device at capture. Call the reporter from your
577
+ app on a natural moment (app open works well):
578
+
579
+ ```tsx
580
+ import { reportAddressPresence, clearPresencePin } from '@myazahq/kyc-sdk-react-native';
581
+
582
+ const result = await reportAddressPresence({
583
+ apiKey: 'pk_live_…',
584
+ externalUserId: 'user_42', // the same userId the KYC flow ran with
585
+ });
586
+ // result.reason: 'reported' | 'no_pin' | 'services_off' | 'no_fix' | 'outside_fence' | 'network_error'
587
+ ```
588
+
589
+ It never throws and never blocks startup. The geofence is evaluated ON-DEVICE:
590
+ only the derived record (calendar day + a night flag) is transmitted, never a
591
+ coordinate. A fix outside the fence sends nothing (the server scores presence,
592
+ never absence); a mock-location fix is reported flagged. `clearPresencePin`
593
+ drops the stored pin (sign-out, or once the watch resolves).
594
+
595
+ ### Background monitoring (OS geofencing)
596
+
597
+ The stronger tier: the OS wakes the SDK on fence crossings around the stored
598
+ pin, app closed or not, so dwell and nights accrue with nobody in the loop.
599
+ Entries stamp a timestamp; exits fold the dwell span into per-day aggregates
600
+ and flush them. As with the foreground tier, only the derived day records
601
+ ever leave the phone.
602
+
603
+ Three opt-ins, each deliberate:
604
+
605
+ 1. Install the optional peer: `npx expo install expo-task-manager` (without
606
+ it the background tier simply does not exist — never a crash).
607
+ 2. Declare background location via the config plugin — this is what changes
608
+ your app's store review posture, so it is never a default:
609
+
610
+ ```json
611
+ ["@myazahq/kyc-sdk-react-native", { "location": "always" }]
612
+ ```
613
+
614
+ 3. Register the task at your app's ROOT module (before the component tree —
615
+ a task defined inside a component never fires headlessly), then enable:
616
+
617
+ ```tsx
618
+ // index.js
619
+ import { registerBackgroundPresence } from '@myazahq/kyc-sdk-react-native';
620
+ registerBackgroundPresence();
621
+
622
+ // later, after the KYC flow stored a pin:
623
+ const result = await enableBackgroundPresence({
624
+ apiKey: 'pk_live_…',
625
+ externalUserId: 'user_42',
626
+ });
627
+ // result.reason: 'enabled' | 'module_missing' | 'no_pin'
628
+ // | 'foreground_denied' | 'background_denied' | 'start_failed'
629
+ ```
630
+
631
+ `disableBackgroundPresence()` disarms the fence. A permission refusal leaves
632
+ the foreground tier working exactly as before — the tiers degrade, never
633
+ break.
634
+
635
+ ### The Android foreground service (reliability on OEM-managed phones)
636
+
637
+ A geofence alone is not reliable on Android once a manufacturer's battery
638
+ manager decides your app is idle: transitions are dropped, nothing says so,
639
+ and the watch quietly lapses to inconclusive. The phones on that list (Tecno,
640
+ Infinix, itel, Xiaomi, Oppo, Vivo) are the ones the market carries. A
641
+ foreground service, with its persistent notification, is the one thing those
642
+ managers leave alone — and OkHi's own integration guidance for the same
643
+ markets is exactly this.
644
+
645
+ Opt-in, Android only (iOS region monitoring is reliable on its own), on the
646
+ same `location: "always"` plugin setting, which also declares the
647
+ `FOREGROUND_SERVICE` and `FOREGROUND_SERVICE_LOCATION` permissions it needs.
648
+ `registerBackgroundPresence()` at the root already defines its task; then:
649
+
650
+ ```tsx
651
+ const result = await enableForegroundService({
652
+ apiKey: 'pk_live_…',
653
+ externalUserId: 'user_42',
654
+ notification: {
655
+ title: 'Address verification in progress', // shown in the status bar
656
+ body: 'Open the app to see your progress',
657
+ color: '#5645F5',
658
+ },
659
+ });
660
+ // result.reason: 'enabled' | 'unsupported_platform' | 'module_missing' | 'no_pin'
661
+ // | 'foreground_denied' | 'background_denied' | 'start_failed'
662
+ ```
663
+
664
+ While it runs, a low-power fix every ten minutes (or hundred metres) is
665
+ turned into the same enter/exit spans the geofence folds, on the same stored
666
+ state, so the two never double-count a stay; the queue flushes while the
667
+ process is alive; and a fence the OS dropped (a location toggle clears every
668
+ registered fence) is re-armed. `disableForegroundService()` stops it and its
669
+ notification. Word the notification honestly — it is on screen for days.
670
+
671
+ ### Which tier is running?
672
+
673
+ Permissions get revoked in Settings and nothing tells the app. Ask:
674
+
675
+ ```tsx
676
+ import { presenceStatus, openLocationSettings } from '@myazahq/kyc-sdk-react-native';
677
+
678
+ const status = await presenceStatus('user_42');
679
+ // status.tier: 'background' | 'foreground' | 'none'
680
+ // plus pinStored, alwaysOn, locationServicesEnabled, both permission states,
681
+ // geofenceArmed, foregroundServiceRunning
682
+ if (!status.locationServicesEnabled) {
683
+ // The phone's location toggle is off: permission granted or not, no fix
684
+ // can be taken. Android deep-links to the toggle itself.
685
+ await openLocationSettings('services');
686
+ } else if (status.tier === 'none' && status.pinStored) {
687
+ // The road back runs through Settings — no OS allows re-prompting in-app.
688
+ await openLocationSettings();
689
+ }
690
+ ```
691
+
692
+ ### Showing the person where the check stands
693
+
694
+ Somebody kept from a feature until their address is verified should be able
695
+ to see the progress in your app, without a webhook relayed through your
696
+ backend. The status endpoint is publishable-safe and enumeration-safe (an
697
+ unknown user and a user with no watch answer the same `not_started` shape):
698
+
699
+ ```ts
700
+ const res = await fetch(`${serverUrl}/api/kyc/address/presence/${externalUserId}`, {
701
+ headers: { Authorization: `Bearer pk_live_…` },
702
+ });
703
+ const { status, progress, tier } = await res.json();
704
+ // status: 'not_started' | 'in_progress' | 'verified' | 'failed' | 'inconclusive' | 'expired' | 'revoked'
705
+ // progress.score: 0..1 on WEIGHTED evidence (five foreground nights and three
706
+ // geofence nights both read 1); nightsObserved/daysObserved beside it
707
+ // tier: 'background' | 'foreground' | null — what is actually feeding it
708
+ ```
@@ -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
- // Google ML Kit face detection — Android only. Same version as the Flutter SDK.
113
- implementation 'com.google.mlkit:face-detection:16.1.6'
114
- // On-device text recognition, for reading the MRZ off a passport or ID card.
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,9 @@ 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.margelo.nitro.NitroModules
14
+ import com.margelo.nitro.core.Promise
12
15
  import java.util.concurrent.CountDownLatch
13
16
  import java.util.concurrent.TimeUnit
14
17
 
@@ -39,6 +42,70 @@ class HybridMyazaFaceDetector : HybridMyazaFaceDetectorSpec() {
39
42
  .build(),
40
43
  )
41
44
 
45
+ // ── Model availability ───────────────────────────────────────────────────
46
+ //
47
+ // The default build fetches ML Kit's models through Play Services instead of
48
+ // bundling them (android/build.gradle), which is what keeps ~18.5 MB per
49
+ // device out of the APK. The cost is a window where detection cannot run:
50
+ // first launch before the download completes, or a device with no GMS at all.
51
+ //
52
+ // `detectFace` CANNOT report that. Its only channel is FaceResult, where a
53
+ // missing model and an empty frame are both `faceCount: 0` — so a user on a
54
+ // fresh install would watch "position your face" forever with nothing to
55
+ // explain it. Hence a separate, explicit contract, checked before the camera
56
+ // opens rather than inferred per frame.
57
+ //
58
+ // Cached rather than queried live: `areModulesAvailable` is asynchronous and
59
+ // `isModelReady` is a synchronous Nitro call, so blocking the worklet thread
60
+ // to answer it would be worse than the problem. The flag starts false and is
61
+ // set by prepareModel(), which the SDK primes at flow start.
62
+ @Volatile private var modelReady = false
63
+
64
+ override fun isModelReady(): Boolean = modelReady
65
+
66
+ override fun prepareModel(): Promise<Boolean> = Promise.async {
67
+ if (modelReady) return@async true
68
+
69
+ val ctx = NitroModules.applicationContext
70
+ ?: return@async false // No context — cannot ask Play Services anything.
71
+
72
+ val latch = CountDownLatch(1)
73
+ var ok = false
74
+ try {
75
+ val client = ModuleInstall.getClient(ctx)
76
+ // areModulesAvailable() answers "is it already here"; deferredInstall()
77
+ // asks Play Services to fetch it in the background if not. Requesting the
78
+ // install unconditionally is simpler AND correct — it is a no-op when the
79
+ // module is already present, and starting it early is the entire point.
80
+ client.areModulesAvailable(detector)
81
+ .addOnSuccessListener { response ->
82
+ ok = response.areModulesAvailable()
83
+ if (!ok) {
84
+ // `deferredInstall` takes the OptionalModuleApi itself, varargs.
85
+ // A ModuleInstallRequest belongs to `installModules`, which is the
86
+ // FOREGROUND install with its own progress listener — a different
87
+ // call, and the one this used to hand its request to, which is why
88
+ // the Android build stopped compiling.
89
+ client.deferredInstall(detector)
90
+ }
91
+ latch.countDown()
92
+ }
93
+ .addOnFailureListener {
94
+ // Thrown on devices without Google Play Services at all (Huawei, bare
95
+ // AOSP). Not an error to retry — it will never succeed on this device.
96
+ // Such orgs should build with `myazaKycBundledMlKit = true`.
97
+ ok = false
98
+ latch.countDown()
99
+ }
100
+ latch.await(MODEL_CHECK_TIMEOUT_MS, TimeUnit.MILLISECONDS)
101
+ } catch (_: Throwable) {
102
+ ok = false
103
+ }
104
+
105
+ modelReady = ok
106
+ ok
107
+ }
108
+
42
109
  @ExperimentalGetImage
43
110
  override fun detectFace(frame: HybridFrameSpec): FaceResult {
44
111
  // Same access pattern as iOS (cast to the public NativeFrame): VisionCamera's
@@ -218,4 +285,14 @@ class HybridMyazaFaceDetector : HybridMyazaFaceDetectorSpec() {
218
285
  faceG = -1.0,
219
286
  faceB = -1.0,
220
287
  )
288
+
289
+ private companion object {
290
+ /**
291
+ * Bound on the availability query. It is a local Play Services call, not the
292
+ * model download — the download runs in the background afterwards. Generous
293
+ * enough for a cold Play Services process, short enough that a wedged one
294
+ * cannot stall flow start.
295
+ */
296
+ const val MODEL_CHECK_TIMEOUT_MS = 3_000L
297
+ }
221
298
  }
package/app.plugin.js CHANGED
@@ -37,6 +37,9 @@ const CAMERA_USAGE =
37
37
  const NFC_USAGE =
38
38
  'We read the secure chip in your passport or ID card to confirm the document is genuine.';
39
39
 
40
+ const LOCATION_USAGE =
41
+ 'We use your location to confirm you are at the address you pin during verification.';
42
+
40
43
  /**
41
44
  * The eMRTD application identifier.
42
45
  *
@@ -62,6 +65,23 @@ function withIosCameraUsage(config, customMessage) {
62
65
  });
63
66
  }
64
67
 
68
+ /**
69
+ * iOS location usage string, for the address-collection step's one-shot fix
70
+ * ("Use my current location" + the attest-presence claim). Written by DEFAULT
71
+ * because its ABSENCE is a crash: iOS kills an app that requests location
72
+ * without a usage string, and whether the step runs is decided by the org's
73
+ * WORKFLOW, not by anything the host app author can see at build time. An
74
+ * unused usage string costs nothing. `location: false` opts out.
75
+ */
76
+ function withIosLocationUsage(config, customMessage) {
77
+ return withInfoPlist(config, (cfg) => {
78
+ if (!cfg.modResults.NSLocationWhenInUseUsageDescription) {
79
+ cfg.modResults.NSLocationWhenInUseUsageDescription = customMessage || LOCATION_USAGE;
80
+ }
81
+ return cfg;
82
+ });
83
+ }
84
+
65
85
  const AID_KEY = 'com.apple.developer.nfc.readersession.iso7816.select-identifiers';
66
86
  const FORMATS_KEY = 'com.apple.developer.nfc.readersession.formats';
67
87
 
@@ -112,24 +132,84 @@ function withIosNfc(config, customMessage) {
112
132
  });
113
133
  }
114
134
 
115
- function withAndroidPermissions(config, { nfc }) {
135
+ /**
136
+ * The Android permission list for a given set of opt-ins. Pure and exported
137
+ * so the tests can pin what `location: 'always'` declares — a permission
138
+ * that changes review posture must never arrive by accident.
139
+ */
140
+ function androidPermissionsFor({ nfc, location, backgroundLocation }) {
141
+ return [
142
+ 'android.permission.CAMERA',
143
+ 'android.permission.INTERNET',
144
+ ...(nfc ? ['android.permission.NFC'] : []),
145
+ // Foreground location, for the address-collection step's one-shot fix.
146
+ ...(location
147
+ ? ['android.permission.ACCESS_COARSE_LOCATION', 'android.permission.ACCESS_FINE_LOCATION']
148
+ : []),
149
+ // The always-on presence tier (OkHi model, opt-in): OS geofencing needs
150
+ // the background grant, and Google Play reviews any app that declares
151
+ // it — which is why this is `location: 'always'` only, never a default.
152
+ // The foreground-service pair rides the same opt-in: FOREGROUND_SERVICE
153
+ // is a normal permission, and FOREGROUND_SERVICE_LOCATION (API 34+) is
154
+ // what lets expo-location's service declare the `location` type; Play
155
+ // reviews it under the same location declaration.
156
+ ...(backgroundLocation
157
+ ? [
158
+ 'android.permission.ACCESS_BACKGROUND_LOCATION',
159
+ 'android.permission.FOREGROUND_SERVICE',
160
+ 'android.permission.FOREGROUND_SERVICE_LOCATION',
161
+ ]
162
+ : []),
163
+ ];
164
+ }
165
+
166
+ function withAndroidPermissions(config, opts) {
116
167
  return withAndroidManifest(config, (cfg) => {
117
- AndroidConfig.Permissions.ensurePermissions(cfg.modResults, [
118
- 'android.permission.CAMERA',
119
- 'android.permission.INTERNET',
120
- ...(nfc ? ['android.permission.NFC'] : []),
121
- ]);
168
+ AndroidConfig.Permissions.ensurePermissions(cfg.modResults, androidPermissionsFor(opts));
169
+ return cfg;
170
+ });
171
+ }
172
+
173
+ /** iOS side of the always-on tier: the Always usage string plus the location
174
+ * background mode, without which a geofence wake is refused. */
175
+ function withIosBackgroundLocation(config, customMessage) {
176
+ return withInfoPlist(config, (cfg) => {
177
+ if (!cfg.modResults.NSLocationAlwaysAndWhenInUseUsageDescription) {
178
+ cfg.modResults.NSLocationAlwaysAndWhenInUseUsageDescription =
179
+ customMessage || LOCATION_USAGE;
180
+ }
181
+ const modes = Array.isArray(cfg.modResults.UIBackgroundModes)
182
+ ? cfg.modResults.UIBackgroundModes
183
+ : [];
184
+ if (!modes.includes('location')) modes.push('location');
185
+ cfg.modResults.UIBackgroundModes = modes;
122
186
  return cfg;
123
187
  });
124
188
  }
125
189
 
126
190
  /**
127
191
  * @param {object} config
128
- * @param {{ cameraPermission?: string, nfc?: boolean, nfcPermission?: string }} [props]
192
+ * @param {{ cameraPermission?: string, nfc?: boolean, nfcPermission?: string,
193
+ * location?: boolean | 'always', locationPermission?: string }} [props]
129
194
  */
130
195
  function withMyazaKyc(config, props = {}) {
196
+ const location = props.location !== false;
197
+ // `location: 'always'` opts into the background presence tier. Explicit on
198
+ // purpose: it changes the app's Play/App Store review posture, so it must
199
+ // never arrive as a side effect of a default.
200
+ const backgroundLocation = props.location === 'always';
131
201
  config = withIosCameraUsage(config, props.cameraPermission);
132
- config = withAndroidPermissions(config, { nfc: props.nfc === true });
202
+ config = withAndroidPermissions(config, {
203
+ nfc: props.nfc === true,
204
+ location,
205
+ backgroundLocation,
206
+ });
207
+ if (location) {
208
+ config = withIosLocationUsage(config, props.locationPermission);
209
+ }
210
+ if (backgroundLocation) {
211
+ config = withIosBackgroundLocation(config, props.locationPermission);
212
+ }
133
213
  if (props.nfc === true) {
134
214
  config = withIosNfc(config, props.nfcPermission);
135
215
  }
@@ -141,4 +221,5 @@ module.exports = createRunOncePlugin(withMyazaKyc, pkg.name, pkg.version);
141
221
  // consumers' app.json entries are unaffected.
142
222
  module.exports.applyNfcInfoPlist = applyNfcInfoPlist;
143
223
  module.exports.applyNfcEntitlements = applyNfcEntitlements;
224
+ module.exports.androidPermissionsFor = androidPermissionsFor;
144
225
  module.exports.EMRTD_AID = EMRTD_AID;
@@ -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,
@@ -295,8 +303,13 @@ final class HybridMyazaFaceDetector: HybridMyazaFaceDetectorSpec {
295
303
  return min(max(v, 0.0), 1.0)
296
304
  }
297
305
 
298
- /// Smile from outer-lip width relative to the face. Input band (0.40→0.50):
299
- /// a neutral mouth sits near 0, a clear smile lands well above the threshold.
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.
300
313
  private static func smileProbability(_ landmarks: VNFaceLandmarks2D?) -> Double {
301
314
  guard let pts = landmarks?.outerLips?.normalizedPoints, pts.count >= 4 else {
302
315
  return 0.0
@@ -307,7 +320,7 @@ final class HybridMyazaFaceDetector: HybridMyazaFaceDetectorSpec {
307
320
  minX = min(minX, Double(p.x)); maxX = max(maxX, Double(p.x))
308
321
  }
309
322
  let width = maxX - minX
310
- let score = (width - 0.40) / (0.50 - 0.40)
323
+ let score = (width - 0.36) / (0.46 - 0.36)
311
324
  return min(max(score, 0.0), 1.0)
312
325
  }
313
326
 
@@ -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;
@@ -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>`.