@myazahq/kyc-sdk-react-native 2.0.2 → 2.1.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 (281) hide show
  1. package/README.md +111 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/AndroidManifest.xml +14 -4
  4. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
  8. package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
  9. package/app.plugin.js +102 -13
  10. package/ios/HybridMyazaEmrtd.swift +447 -0
  11. package/ios/HybridMyazaFaceDetector.swift +104 -2
  12. package/ios/HybridMyazaRectDetector.swift +103 -0
  13. package/ios/HybridMyazaTextRecognizer.swift +111 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
  16. package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
  18. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
  19. package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
  20. package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
  21. package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
  22. package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
  23. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
  24. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
  26. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
  27. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
  28. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
  29. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
  30. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
  31. package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
  42. package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
  44. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
  45. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
  46. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
  47. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
  48. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
  49. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
  50. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
  51. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
  52. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
  54. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
  56. package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
  57. package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
  58. package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
  59. package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
  60. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  61. package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
  62. package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
  63. package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
  64. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  66. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
  67. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
  68. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
  70. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
  71. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
  72. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
  73. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
  74. package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
  75. package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
  76. package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
  77. package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
  78. package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
  79. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
  80. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
  81. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
  82. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
  83. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
  84. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
  85. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
  86. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
  87. package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
  88. package/package.json +16 -4
  89. package/src/MyazaKYC.tsx +115 -22
  90. package/src/capture/documentIdentity.ts +153 -0
  91. package/src/capture/documentSignals.ts +177 -0
  92. package/src/capture/documentTextGate.ts +171 -0
  93. package/src/capture/hints.ts +57 -0
  94. package/src/capture/index.ts +24 -0
  95. package/src/capture/rectDetector.ts +84 -0
  96. package/src/capture/rectGate.ts +91 -0
  97. package/src/capture/useAutoCapture.ts +198 -0
  98. package/src/components/CameraViewfinder.tsx +226 -70
  99. package/src/components/ContactCodeEntry.tsx +139 -0
  100. package/src/components/CountryField.tsx +87 -0
  101. package/src/components/CountryFlag.tsx +15 -15
  102. package/src/components/DialCodePicker.tsx +186 -0
  103. package/src/components/DocumentCropper.tsx +1 -1
  104. package/src/components/DocumentReview.tsx +142 -0
  105. package/src/components/DocumentReviewSide.tsx +135 -0
  106. package/src/components/DocumentReviewZoom.tsx +119 -0
  107. package/src/components/ExpiryCountdown.tsx +52 -0
  108. package/src/components/FlashOverlay.tsx +156 -0
  109. package/src/components/Icon.tsx +67 -1
  110. package/src/components/KycFlow.tsx +66 -94
  111. package/src/components/KycSheet.tsx +148 -18
  112. package/src/components/MediaSourceSheet.tsx +163 -0
  113. package/src/components/MyazaButton.tsx +14 -2
  114. package/src/components/MyazaDateField.tsx +240 -0
  115. package/src/components/MyazaInput.tsx +73 -26
  116. package/src/components/MyazaSelect.tsx +283 -0
  117. package/src/components/MyazaWordmark.tsx +33 -0
  118. package/src/components/OptionRow.tsx +132 -0
  119. package/src/components/PhoneNumberInput.tsx +149 -0
  120. package/src/components/PoweredBy.tsx +89 -0
  121. package/src/components/ReadyPrimer.tsx +149 -0
  122. package/src/components/StepView.tsx +91 -0
  123. package/src/components/Typography.tsx +18 -3
  124. package/src/components/VerifiedNotice.tsx +38 -0
  125. package/src/components/WhatsAppIcon.tsx +29 -0
  126. package/src/components/WorkflowGate.tsx +128 -0
  127. package/src/components/brand-font.ts +113 -0
  128. package/src/components/flashHoleGeometry.ts +35 -0
  129. package/src/components/flow/FatalConfigError.tsx +56 -0
  130. package/src/components/fonts.ts +2 -19
  131. package/src/components/readyPrimerContent.ts +52 -0
  132. package/src/components/runtime.tsx +38 -43
  133. package/src/components/stepHeaderMeta.tsx +87 -0
  134. package/src/components/theme-provider.tsx +89 -0
  135. package/src/components/useWorkflowMount.ts +116 -0
  136. package/src/components/viewfinder/CardGuide.tsx +60 -0
  137. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  138. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  139. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  140. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  141. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  142. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  143. package/src/config/brand.ts +97 -0
  144. package/src/config/business.ts +179 -0
  145. package/src/config/businessSteps.ts +105 -0
  146. package/src/config/contact.ts +159 -0
  147. package/src/config/countryNames.g.ts +254 -0
  148. package/src/config/currencyFlags.ts +144 -0
  149. package/src/config/font-resolve.ts +78 -0
  150. package/src/config/idTypes.ts +96 -1
  151. package/src/config/keyPeople.ts +152 -0
  152. package/src/config/phone.ts +39 -0
  153. package/src/config/proofOfAddress.ts +75 -0
  154. package/src/config/questionnaire.ts +153 -0
  155. package/src/config/regions.ts +118 -0
  156. package/src/config/registrationHint.ts +87 -0
  157. package/src/config/stepOrder.ts +118 -0
  158. package/src/config/theme.ts +38 -2
  159. package/src/config/workflowMerge.ts +124 -0
  160. package/src/emrtd/bac.ts +128 -0
  161. package/src/emrtd/bytes.ts +127 -0
  162. package/src/emrtd/crypto.ts +163 -0
  163. package/src/emrtd/der.ts +192 -0
  164. package/src/emrtd/dg2.ts +111 -0
  165. package/src/emrtd/files.ts +215 -0
  166. package/src/emrtd/index.ts +30 -0
  167. package/src/emrtd/mrzKey.ts +54 -0
  168. package/src/emrtd/native.ts +79 -0
  169. package/src/emrtd/optionalRead.ts +42 -0
  170. package/src/emrtd/read.ts +224 -0
  171. package/src/emrtd/retry.ts +98 -0
  172. package/src/emrtd/secureMessaging.ts +178 -0
  173. package/src/emrtd/session.ts +200 -0
  174. package/src/emrtd/stages.ts +122 -0
  175. package/src/index.ts +110 -0
  176. package/src/lib/calendar.ts +71 -0
  177. package/src/lib/use-keyboard-inset.ts +34 -0
  178. package/src/liveness/challengeManager.ts +27 -1
  179. package/src/liveness/faceContinuity.ts +153 -0
  180. package/src/liveness/flashDetector.ts +145 -0
  181. package/src/liveness/flashReadyGate.ts +106 -0
  182. package/src/liveness/flashRunner.ts +118 -0
  183. package/src/liveness/integritySignals.ts +60 -0
  184. package/src/liveness/livenessState.ts +139 -0
  185. package/src/liveness/speech.ts +19 -1
  186. package/src/liveness/types.ts +48 -1
  187. package/src/liveness/useFlashPhase.ts +78 -0
  188. package/src/liveness/useLiveness.ts +260 -75
  189. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  190. package/src/mrz/extract.ts +101 -0
  191. package/src/mrz/parse.ts +178 -0
  192. package/src/mrz/textRecognizer.ts +149 -0
  193. package/src/screens/ApplicantRoleStep.tsx +331 -0
  194. package/src/screens/BusinessDetailsStep.tsx +212 -0
  195. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  196. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  197. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  198. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  199. package/src/screens/CompanyInfoFields.tsx +83 -0
  200. package/src/screens/ConsentStep.tsx +68 -68
  201. package/src/screens/ContactChannelChoice.tsx +133 -0
  202. package/src/screens/ContactCodeStep.tsx +130 -0
  203. package/src/screens/ContactDestinationField.tsx +70 -0
  204. package/src/screens/ContactFooterNote.tsx +34 -0
  205. package/src/screens/ContactVerificationStep.tsx +199 -0
  206. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  207. package/src/screens/CountrySelectStep.tsx +210 -0
  208. package/src/screens/DocumentCaptureStep.tsx +156 -132
  209. package/src/screens/IdInputStep.tsx +13 -5
  210. package/src/screens/IdTypeStep.tsx +74 -33
  211. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  212. package/src/screens/LivenessStep.tsx +155 -247
  213. package/src/screens/MrzScanView.tsx +173 -0
  214. package/src/screens/NfcStep.tsx +290 -0
  215. package/src/screens/ProofOfAddressStep.tsx +238 -0
  216. package/src/screens/QuestionnaireField.tsx +219 -0
  217. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  218. package/src/screens/QuestionnaireStep.tsx +76 -0
  219. package/src/screens/SubmittedStep.tsx +5 -0
  220. package/src/screens/consent/model.ts +109 -0
  221. package/src/screens/document/CameraPhase.tsx +150 -0
  222. package/src/screens/document/RequiredPill.tsx +60 -0
  223. package/src/screens/liveness/LightingBanner.tsx +50 -0
  224. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  225. package/src/screens/liveness/ProgressDots.tsx +67 -0
  226. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  227. package/src/screens/liveness/constants.ts +31 -0
  228. package/src/screens/liveness/index.ts +8 -0
  229. package/src/screens/liveness/resolveGuidance.ts +27 -0
  230. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  231. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  232. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  233. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  234. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  235. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  236. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  237. package/src/screens/nfc/availability.ts +73 -0
  238. package/src/screens/nfc/readErrorMessage.ts +93 -0
  239. package/src/screens/nfc/useChipPortrait.ts +58 -0
  240. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  241. package/src/screens/useFlashHole.ts +105 -0
  242. package/src/screens/useFlashSequence.ts +96 -0
  243. package/src/screens/usePoaAttach.ts +121 -0
  244. package/src/services/api-types.ts +164 -0
  245. package/src/services/api-verify-types.ts +99 -0
  246. package/src/services/api.ts +72 -102
  247. package/src/services/cardCrop.ts +25 -10
  248. package/src/services/contactErrors.ts +43 -0
  249. package/src/services/deviceMetadata.ts +1 -1
  250. package/src/services/documentPicker.ts +20 -0
  251. package/src/services/fingerprint-sources.ts +129 -0
  252. package/src/services/fingerprint.ts +101 -0
  253. package/src/services/haptics.ts +38 -0
  254. package/src/services/mediaCompress.ts +7 -2
  255. package/src/services/resolveUrl.ts +2 -2
  256. package/src/services/workflowGate.ts +138 -0
  257. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  258. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  259. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  260. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  261. package/src/store/derive.ts +225 -0
  262. package/src/store/kycStore.ts +250 -149
  263. package/src/store/state.ts +253 -0
  264. package/src/store/submit.ts +101 -0
  265. package/src/store/submitApplicant.ts +81 -0
  266. package/src/types/appearance.ts +113 -0
  267. package/src/types/business.ts +131 -0
  268. package/src/types/config.ts +150 -124
  269. package/src/types/id-types.ts +57 -0
  270. package/src/types/verification.ts +5 -0
  271. package/src/types/workflow.ts +141 -0
  272. package/src/utils/amountFormat.ts +73 -0
  273. package/src/utils/tokens.ts +4 -2
  274. package/src/__tests__/cardCrop.test.ts +0 -39
  275. package/src/__tests__/deviceMetadata.test.ts +0 -34
  276. package/src/__tests__/errors.test.ts +0 -34
  277. package/src/__tests__/flow.test.ts +0 -61
  278. package/src/__tests__/gestureDetector.test.ts +0 -37
  279. package/src/__tests__/liveness.test.ts +0 -112
  280. package/src/__tests__/resolveUrl.test.ts +0 -64
  281. package/src/__tests__/validators.test.ts +0 -38
@@ -0,0 +1,200 @@
1
+ import { fromBase64, toBase64 } from './bytes';
2
+ import { buildBacChallenge, completeBac } from './bac';
3
+ import { primitivesFromNative, type EmrtdPrimitives, type MrzKeyFields } from './crypto';
4
+ import { EF, readFile, type Transceive } from './files';
5
+ import { readOptionalFile } from './optionalRead';
6
+ import { SecureMessagingSession } from './secureMessaging';
7
+ import type { NfcReadStage } from './stages';
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Reading a chip, end to end.
11
+ //
12
+ // Select the application, run BAC, then read the files. What comes back is
13
+ // handed to the SERVER, which is where authenticity is decided: the server
14
+ // hashes DG1 against the signed security object and checks the document
15
+ // signer's signature. Nothing here may conclude that a chip is genuine — a
16
+ // client-side "authentic" flag is a claim an attacker controls.
17
+ //
18
+ // So the job of this file is narrow and honest: get the bytes off the chip, and
19
+ // report clearly when it could not.
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /** The eMRTD application identifier — selected before anything else works. */
23
+ const AID = new Uint8Array([0xa0, 0x00, 0x00, 0x02, 0x47, 0x10, 0x01]);
24
+
25
+ const SW_OK = 0x9000;
26
+
27
+ export class EmrtdSessionError extends Error {
28
+ constructor(
29
+ message: string,
30
+ /** A stable token the UI branches on, rather than matching prose. */
31
+ readonly code: 'select_failed' | 'bac_failed' | 'read_failed' | 'transport',
32
+ ) {
33
+ super(message);
34
+ this.name = 'EmrtdSessionError';
35
+ Object.setPrototypeOf(this, EmrtdSessionError.prototype);
36
+ }
37
+ }
38
+
39
+ /** What a successful read yields, all base64 for the submission. */
40
+ export interface EmrtdReadResult {
41
+ /** DG1 — the MRZ as the chip stores it. Always present on success. */
42
+ dg1: string;
43
+ /** EF.SOD — the signed security object. The server needs it to verify DG1. */
44
+ sod?: string;
45
+ /** DG2 — the portrait. Best-effort; the largest file and the likeliest to drop. */
46
+ dg2?: string;
47
+ /** How the chip was unlocked. */
48
+ chipAuth: 'bac';
49
+ }
50
+
51
+ /** The raw transport, before secure messaging wraps it. */
52
+ export interface EmrtdTransport {
53
+ /** Send an APDU, get data + status word back. */
54
+ transceive(apduBase64: string): Promise<{ data: string; statusWord: number }>;
55
+ }
56
+
57
+ /** SELECT the eMRTD application. Nothing else responds until this succeeds. */
58
+ async function selectApplication(transport: EmrtdTransport): Promise<void> {
59
+ const apdu = new Uint8Array([0x00, 0xa4, 0x04, 0x0c, AID.length, ...AID]);
60
+ const { statusWord } = await transport.transceive(toBase64(apdu));
61
+ if (statusWord !== SW_OK) {
62
+ throw new EmrtdSessionError(
63
+ `The chip did not accept the passport application (0x${statusWord.toString(16)}).`,
64
+ 'select_failed',
65
+ );
66
+ }
67
+ }
68
+
69
+ /** GET CHALLENGE — the chip's 8-byte nonce, which starts BAC. */
70
+ async function getChallenge(transport: EmrtdTransport): Promise<Uint8Array> {
71
+ const { data, statusWord } = await transport.transceive(
72
+ toBase64(new Uint8Array([0x00, 0x84, 0x00, 0x00, 0x08])),
73
+ );
74
+ if (statusWord !== SW_OK) {
75
+ throw new EmrtdSessionError(
76
+ `The chip refused to issue a challenge (0x${statusWord.toString(16)}).`,
77
+ 'bac_failed',
78
+ );
79
+ }
80
+ return fromBase64(data);
81
+ }
82
+
83
+ /**
84
+ * Run BAC and open a secure-messaging session.
85
+ *
86
+ * A failure here means the MRZ was read wrong, or the thing answering is not
87
+ * the document — NOT that the read should be retried with the same key.
88
+ */
89
+ async function openSession(
90
+ p: EmrtdPrimitives,
91
+ transport: EmrtdTransport,
92
+ mrz: MrzKeyFields,
93
+ ): Promise<SecureMessagingSession> {
94
+ const rndIc = await getChallenge(transport);
95
+ const challenge = buildBacChallenge(p, mrz, rndIc);
96
+
97
+ const apdu = new Uint8Array([
98
+ 0x00,
99
+ 0x82,
100
+ 0x00,
101
+ 0x00,
102
+ challenge.command.length,
103
+ ...challenge.command,
104
+ 0x28,
105
+ ]);
106
+ const { data, statusWord } = await transport.transceive(toBase64(apdu));
107
+ if (statusWord !== SW_OK) {
108
+ throw new EmrtdSessionError(
109
+ 'The chip rejected the key derived from the document. Check the scanned details.',
110
+ 'bac_failed',
111
+ );
112
+ }
113
+
114
+ const { keys, ssc } = completeBac(p, challenge, fromBase64(data));
115
+ return new SecureMessagingSession(p, keys, ssc);
116
+ }
117
+
118
+ /**
119
+ * Read the chip.
120
+ *
121
+ * DG1 is required — without the MRZ there is nothing to verify. EF.SOD and DG2
122
+ * are BEST-EFFORT and in that order: the SOD is what makes DG1 trustworthy, and
123
+ * DG2 is only worth reading if the SOD arrived, since an unverifiable portrait
124
+ * is one an attacker could have substituted.
125
+ */
126
+ export async function readChip(
127
+ native: Parameters<typeof primitivesFromNative>[0] & EmrtdTransport,
128
+ mrz: MrzKeyFields,
129
+ /**
130
+ * Progress, so the UI can narrate a read that is otherwise invisible. Each
131
+ * call marks the step ABOUT to run, not the one just finished — the user
132
+ * needs to know what is happening now, and EF.SOD in particular takes long
133
+ * enough that silence reads as failure and prompts them to lift the document.
134
+ */
135
+ onStage?: (stage: NfcReadStage) => void,
136
+ ): Promise<EmrtdReadResult> {
137
+ const p = primitivesFromNative(native);
138
+
139
+ onStage?.('authenticating');
140
+ // SELECT + BAC are retried ONCE in place, on the same live connection.
141
+ // On Android the tag is routinely dispatched while the platform is still
142
+ // settling the link, so the first exchange dies with the chip right there —
143
+ // and an immediate second attempt succeeds with the phone untouched. The
144
+ // Flutter SDK documents and fixes this exact failure the same way
145
+ // (nfc_reader_emrtd.dart); a full session teardown cannot fix it, because
146
+ // Android never re-dispatches a tag that stayed in the field, so the outer
147
+ // retry loop just waited for a re-tap nobody knew to perform.
148
+ let sm: Awaited<ReturnType<typeof openSession>> | null = null;
149
+ for (let attempt = 0; sm === null; attempt += 1) {
150
+ try {
151
+ await selectApplication(native);
152
+ sm = await openSession(p, native, mrz);
153
+ } catch (err) {
154
+ if (attempt >= 1) throw err;
155
+ }
156
+ }
157
+
158
+ const transceive: Transceive = async (command) => {
159
+ const { data, statusWord } = await native.transceive(toBase64(command));
160
+ // The wrapped response is what secure messaging verifies; the outer status
161
+ // word is not covered by the MAC and is not the one that counts.
162
+ const body = fromBase64(data);
163
+ const out = new Uint8Array(body.length + 2);
164
+ out.set(body);
165
+ out[body.length] = (statusWord >> 8) & 0xff;
166
+ out[body.length + 1] = statusWord & 0xff;
167
+ return out;
168
+ };
169
+
170
+ onStage?.('readingData');
171
+ let dg1: Uint8Array;
172
+ try {
173
+ dg1 = await readFile(sm, transceive, EF.DG1);
174
+ } catch (err) {
175
+ throw new EmrtdSessionError(
176
+ err instanceof Error ? err.message : 'The chip stopped responding.',
177
+ 'read_failed',
178
+ );
179
+ }
180
+
181
+ // From here on, a failure costs a signal rather than the read. A chip that
182
+ // gave up its MRZ has already proven it holds the BAC key. Optional reads
183
+ // get a second in-session try and NAME their failure — see optionalRead.ts.
184
+ onStage?.('readingSecurity');
185
+ const sod = await readOptionalFile(sm, transceive, EF.SOD, 'EF.SOD');
186
+
187
+ let dg2: Uint8Array | null = null;
188
+ if (sod) {
189
+ onStage?.('readingPhoto');
190
+ dg2 = await readOptionalFile(sm, transceive, EF.DG2, 'DG2');
191
+ }
192
+ onStage?.('done');
193
+
194
+ return {
195
+ dg1: toBase64(dg1),
196
+ ...(sod ? { sod: toBase64(sod) } : {}),
197
+ ...(dg2 ? { dg2: toBase64(dg2) } : {}),
198
+ chipAuth: 'bac',
199
+ };
200
+ }
@@ -0,0 +1,122 @@
1
+ // ---------------------------------------------------------------------------
2
+ // How far a chip read has got.
3
+ //
4
+ // iOS puts a system sheet over the whole read and narrates it. Android has NO
5
+ // system NFC UI at all, so the same read is otherwise a bare spinner: the user
6
+ // cannot tell whether the chip has even been detected, let alone that three
7
+ // data groups are being pulled off it — and the natural response to that
8
+ // silence is to lift the document, which aborts the read.
9
+ //
10
+ // The wording is deliberately identical to the iOS sheet's, so both platforms
11
+ // narrate the read the same way. Ported from the Flutter SDK's NfcReadStage —
12
+ // keep the two in step.
13
+ // ---------------------------------------------------------------------------
14
+
15
+ export type NfcReadStage =
16
+ /** Session open, waiting for the user to actually present the chip. */
17
+ | 'waiting'
18
+ /** Chip detected; opening the BAC session with the MRZ key. */
19
+ | 'authenticating'
20
+ /** Reading DG1 — the document's printed details. */
21
+ | 'readingData'
22
+ /** Reading EF.SOD — kilobytes, so the long one, and the step users abandon. */
23
+ | 'readingSecurity'
24
+ /** Reading DG2 — the chip's portrait. */
25
+ | 'readingPhoto'
26
+ /** Everything that could be read has been. */
27
+ | 'done';
28
+
29
+ /** Order of the read, used to decide which steps are already complete. */
30
+ export const NFC_STAGE_ORDER: NfcReadStage[] = [
31
+ 'waiting',
32
+ 'authenticating',
33
+ 'readingData',
34
+ 'readingSecurity',
35
+ 'readingPhoto',
36
+ 'done',
37
+ ];
38
+
39
+ /** Short label for the step list. */
40
+ export function nfcStageLabel(stage: NfcReadStage): string {
41
+ switch (stage) {
42
+ case 'waiting':
43
+ return 'Waiting for the chip';
44
+ case 'authenticating':
45
+ return 'Unlocking the chip';
46
+ case 'readingData':
47
+ return 'Reading document details';
48
+ case 'readingSecurity':
49
+ return 'Reading security data';
50
+ case 'readingPhoto':
51
+ return 'Reading photo';
52
+ case 'done':
53
+ return 'Chip read complete';
54
+ }
55
+ }
56
+
57
+ /**
58
+ * One line on what the phone is actually doing.
59
+ *
60
+ * A chip read is invisible — no shutter, no preview — so the detail is what
61
+ * stops a pause reading as a failure and prompting the user to lift the
62
+ * document.
63
+ */
64
+ export function nfcStageDetail(stage: NfcReadStage): string {
65
+ switch (stage) {
66
+ case 'waiting':
67
+ return 'Hold the top edge of your document flat against the back of your phone.';
68
+ case 'authenticating':
69
+ return 'Opening a secure session using the code printed on your photo page.';
70
+ case 'readingData':
71
+ return 'Copying the details stored on the chip.';
72
+ case 'readingSecurity':
73
+ return 'Downloading the chip’s digital signature. This is the largest part and takes the longest — keep holding.';
74
+ case 'readingPhoto':
75
+ return 'Copying the photo stored on the chip.';
76
+ case 'done':
77
+ return 'Everything was read successfully.';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * The line shown on iOS's system NFC sheet while a stage runs.
83
+ *
84
+ * The sheet COVERS the app during the read, so this is the only narration an
85
+ * iOS user gets. Terser than the in-app detail — the sheet truncates — and the
86
+ * long transfers say "keep holding", because lifting during them is exactly
87
+ * how the security file and the photo get lost.
88
+ */
89
+ export function nfcSheetMessage(stage: NfcReadStage): string {
90
+ switch (stage) {
91
+ case 'waiting':
92
+ return 'Hold your phone near the passport';
93
+ case 'authenticating':
94
+ return 'Chip found — unlocking it…';
95
+ case 'readingData':
96
+ return 'Reading document details…';
97
+ case 'readingSecurity':
98
+ return 'Verifying security data — keep holding…';
99
+ case 'readingPhoto':
100
+ return 'Reading your photo — keep holding…';
101
+ case 'done':
102
+ return 'Chip read complete';
103
+ }
104
+ }
105
+
106
+ /** Position in the read, 0..1 — drives the progress bar. */
107
+ export function nfcStageProgress(stage: NfcReadStage): number {
108
+ switch (stage) {
109
+ case 'waiting':
110
+ return 0;
111
+ case 'authenticating':
112
+ return 0.15;
113
+ case 'readingData':
114
+ return 0.35;
115
+ case 'readingSecurity':
116
+ return 0.6;
117
+ case 'readingPhoto':
118
+ return 0.85;
119
+ case 'done':
120
+ return 1;
121
+ }
122
+ }
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export type { MyazaKYCProps, UseMyazaKYCReturn } from './MyazaKYC';
14
14
  // Public config + callback types
15
15
  export type {
16
16
  MyazaKYCConfig,
17
+ ResolvedKYCConfig,
17
18
  SupportedCountry,
18
19
  IdType,
19
20
  IdTypeForCountry,
@@ -24,7 +25,36 @@ export type {
24
25
  KYCSuccessContent,
25
26
  VoiceGuidanceConfig,
26
27
  VoiceGuidanceOption,
28
+ // Workflow-driven blocks — normally authored in the dashboard builder and
29
+ // delivered by `workflowId`, but settable as props too.
30
+ EmailVerificationConfig,
31
+ PhoneVerificationConfig,
32
+ ProofOfAddressConfig,
33
+ PoaDocumentType,
34
+ QuestionnaireConfig,
35
+ QuestionnaireField,
36
+ QuestionnaireFieldOption,
37
+ QuestionnaireAnswerValue,
38
+ OtpInputStyle,
39
+ NfcConfig,
40
+ LivenessMode,
41
+ WorkflowCountry,
42
+ WorkflowIdOption,
27
43
  } from './types/config';
44
+ export type {
45
+ SubjectType,
46
+ ApplicantRole,
47
+ KeyPersonRole,
48
+ KeyPeopleLevel,
49
+ BusinessDocumentKey,
50
+ CompanyInfoField,
51
+ CompanyInfoMode,
52
+ WorkflowBusinessConfig,
53
+ WorkflowKeyPeopleConfig,
54
+ WorkflowBusinessDocumentsConfig,
55
+ WorkflowBusinessDocumentTypeConfig,
56
+ WorkflowBusinessApplicantConfig,
57
+ } from './types/business';
28
58
  export type { KYCSubmission, KYCErrorCode, KYCErrorDetails } from './types/verification';
29
59
  export { KYCError } from './types/verification';
30
60
 
@@ -35,8 +65,51 @@ export {
35
65
  isNumberOnlyIdType,
36
66
  requiresDocumentCapture,
37
67
  getScanSides,
68
+ supportsNfcChip,
38
69
  } from './config/idTypes';
39
70
 
71
+ // Flow shape — the single ordered list navigation, the back button and the
72
+ // progress indicator all read.
73
+ export {
74
+ buildStepOrder,
75
+ getStepProgress,
76
+ nextStepInOrder,
77
+ previousStepInOrder,
78
+ type StepOrderOptions,
79
+ } from './config/stepOrder';
80
+
81
+ // Business (KYB) product catalogue + application gates
82
+ export {
83
+ BUSINESS_PRODUCTS,
84
+ DEFAULT_BUSINESS_PRODUCT,
85
+ businessCountriesFor,
86
+ businessProductsFor,
87
+ businessProductsForCountry,
88
+ companyInfoFieldModes,
89
+ getBusinessProductDef,
90
+ isBusinessFlow,
91
+ type BusinessProductDef,
92
+ } from './config/business';
93
+ export {
94
+ businessSectionSteps,
95
+ hasApplicantVerification,
96
+ hasBusinessDocumentsStep,
97
+ hasKeyPeopleCollection,
98
+ keyPeopleMinEntries,
99
+ resolveBusinessDocumentTypes,
100
+ type BusinessSectionStep,
101
+ type ResolvedBusinessDocumentType,
102
+ } from './config/businessSteps';
103
+
104
+ // Optional-step gates — the same helpers the step order reads, exported so a
105
+ // consumer can reason about which steps a given config will run.
106
+ export { hasActiveQuestionnaire, questionnaireAnswerKeys } from './config/questionnaire';
107
+ export { hasEmailVerificationStep, hasPhoneVerificationStep } from './config/contact';
108
+ export { hasProofOfAddressStep, poaDocumentTypes, poaTypeLabel } from './config/proofOfAddress';
109
+
110
+ // Country grouping for multi-region flows
111
+ export { groupCountriesByRegion, regionCountryName, type Region } from './config/regions';
112
+
40
113
  // Client-side validation
41
114
  export { validateIdNumber, maskIdNumber, type ValidationResult } from './services/validators';
42
115
 
@@ -50,5 +123,42 @@ export {
50
123
  hasFaceDetectorFactory,
51
124
  } from './liveness/faceDetector';
52
125
 
126
+ // Device Intelligence — the fingerprint the server scores. Exported so a
127
+ // consumer can see exactly what is collected.
128
+ export {
129
+ collectFingerprint,
130
+ type ClientFingerprint,
131
+ type FingerprintComponents,
132
+ } from './services/fingerprint';
133
+
134
+ // MRZ reading (ICAO 9303) — exported so a consumer can parse a document's
135
+ // machine-readable zone with the same check-digit validation the SDK uses.
136
+ export { mrzCheckDigit, parseMrz, type MrzScan } from './mrz/parse';
137
+ // Capability probes, so a consumer can tell "this build cannot read text" from
138
+ // "this document has no MRZ" — the two look identical from the outside, and
139
+ // only the first is worth reporting as a build problem.
140
+ export { hasTextRecognizer } from './mrz/textRecognizer';
141
+ export { hasRectDetector } from './capture/rectDetector';
142
+ export { extractMrz, sanitizeMrzLine } from './mrz/extract';
143
+
144
+ // eMRTD chip reading. The SERVER decides authenticity — it hashes DG1 against
145
+ // the signed security object and verifies the document signer. Nothing here
146
+ // concludes that a chip is genuine.
147
+ export {
148
+ isNfcAvailable,
149
+ readPassportChip,
150
+ EmrtdSessionError,
151
+ type EmrtdReadResult,
152
+ } from './emrtd';
153
+
154
+ // Flash (screen-reflection) liveness — the palette and correlation are a
155
+ // contract with the server's own re-analysis of the recorded video.
156
+ export {
157
+ FLASH_PALETTE,
158
+ generateFlashSequence,
159
+ type FlashColor,
160
+ type FlashResult,
161
+ } from './liveness/flashDetector';
162
+
53
163
  // SDK version
54
164
  export { SDK_VERSION } from './services/deviceMetadata';
@@ -0,0 +1,71 @@
1
+ /**
2
+ * PURE month-grid math for the date picker — no RN imports, so it is unit
3
+ * testable. Dates are handled as plain {year, month0, day} numbers and ISO
4
+ * `YYYY-MM-DD` strings; never Date objects at the edges, because
5
+ * `new Date('YYYY-MM-DD')` parses as UTC midnight and shifts a birthday by a
6
+ * day for every user west of Greenwich.
7
+ */
8
+
9
+ export const MONTH_NAMES = [
10
+ 'January', 'February', 'March', 'April', 'May', 'June',
11
+ 'July', 'August', 'September', 'October', 'November', 'December',
12
+ ] as const;
13
+
14
+ export const WEEKDAY_INITIALS = ['S', 'M', 'T', 'W', 'T', 'F', 'S'] as const;
15
+
16
+ export interface CalendarDate {
17
+ year: number;
18
+ /** 0-based, matching Date#getMonth. */
19
+ month0: number;
20
+ day: number;
21
+ }
22
+
23
+ export function daysInMonth(year: number, month0: number): number {
24
+ return new Date(year, month0 + 1, 0).getDate();
25
+ }
26
+
27
+ /**
28
+ * The weeks of one month, Sunday-first, padded with nulls so every row is
29
+ * seven cells — the shape a calendar grid renders directly.
30
+ */
31
+ export function monthMatrix(year: number, month0: number): (number | null)[][] {
32
+ const firstWeekday = new Date(year, month0, 1).getDay();
33
+ const total = daysInMonth(year, month0);
34
+
35
+ const cells: (number | null)[] = [
36
+ ...Array.from({ length: firstWeekday }, () => null),
37
+ ...Array.from({ length: total }, (_, i) => i + 1),
38
+ ];
39
+ while (cells.length % 7 !== 0) cells.push(null);
40
+
41
+ const weeks: (number | null)[][] = [];
42
+ for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
43
+ return weeks;
44
+ }
45
+
46
+ export function toIsoDate({ year, month0, day }: CalendarDate): string {
47
+ const mm = String(month0 + 1).padStart(2, '0');
48
+ const dd = String(day).padStart(2, '0');
49
+ return `${year}-${mm}-${dd}`;
50
+ }
51
+
52
+ /** Strictly `YYYY-MM-DD` and a real calendar day — anything else is null. */
53
+ export function parseIsoDate(value: string | undefined | null): CalendarDate | null {
54
+ if (!value) return null;
55
+ const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value.trim());
56
+ if (!m) return null;
57
+ const year = Number(m[1]);
58
+ const month0 = Number(m[2]) - 1;
59
+ const day = Number(m[3]);
60
+ if (month0 < 0 || month0 > 11) return null;
61
+ if (day < 1 || day > daysInMonth(year, month0)) return null;
62
+ return { year, month0, day };
63
+ }
64
+
65
+ /** Step a (year, month0) cursor by ±1 month, carrying across year edges. */
66
+ export function addMonth(year: number, month0: number, delta: 1 | -1): { year: number; month0: number } {
67
+ const next = month0 + delta;
68
+ if (next < 0) return { year: year - 1, month0: 11 };
69
+ if (next > 11) return { year: year + 1, month0: 0 };
70
+ return { year, month0: next };
71
+ }
@@ -0,0 +1,34 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { Keyboard, Platform } from 'react-native';
3
+
4
+ /**
5
+ * The height the software keyboard currently covers, as a bottom inset.
6
+ *
7
+ * Exists for ANDROID. The flow presents inside a `statusBarTranslucent`
8
+ * `<Modal>`, and on Android that combination disables `adjustResize` — the
9
+ * window never shrinks for the keyboard, so a ScrollView inside it never
10
+ * learns the bottom half of the screen is gone and the focused input stays
11
+ * buried. (iOS doesn't use this: the ScrollView's
12
+ * `automaticallyAdjustKeyboardInsets` is the platform's own, better handling —
13
+ * it insets AND reveals the focused input.)
14
+ *
15
+ * Padding the scroll content by this amount restores what adjustResize would
16
+ * have done: everything above the keyboard is reachable by scrolling.
17
+ */
18
+ export function useKeyboardInset(): number {
19
+ const [inset, setInset] = useState(0);
20
+
21
+ useEffect(() => {
22
+ if (Platform.OS !== 'android') return undefined;
23
+ const show = Keyboard.addListener('keyboardDidShow', (e) =>
24
+ setInset(e.endCoordinates.height),
25
+ );
26
+ const hide = Keyboard.addListener('keyboardDidHide', () => setInset(0));
27
+ return () => {
28
+ show.remove();
29
+ hide.remove();
30
+ };
31
+ }, []);
32
+
33
+ return inset;
34
+ }
@@ -10,6 +10,7 @@ import {
10
10
  type ChallengeConfig,
11
11
  type LivenessChallenge,
12
12
  type LivenessConfig,
13
+ type LivenessMode,
13
14
  } from './types';
14
15
 
15
16
  // Gestures within a similarity group should never appear together — one can
@@ -29,9 +30,34 @@ function shuffle<T>(arr: readonly T[]): T[] {
29
30
  return copy;
30
31
  }
31
32
 
32
- /** Picks `challengeCount` non-similar challenges from the pool, in random order. */
33
+ /**
34
+ * Whether the workflow's liveness mode runs gesture challenges at all.
35
+ *
36
+ * `flash` means the screen-reflection sequence IS the check, so gestures are
37
+ * skipped entirely — only `both` runs gestures and then flash. Kept in step
38
+ * with the Flutter SDK's `runsGestures` and the web SDK.
39
+ */
40
+ export function modeRunsGestures(mode: LivenessMode | undefined): boolean {
41
+ return mode !== 'flash';
42
+ }
43
+
44
+ /**
45
+ * Picks `challengeCount` non-similar challenges from the pool, in random order.
46
+ *
47
+ * Returns NOTHING in flash-only mode. This is gated here, at the single place
48
+ * challenges are chosen, rather than at the call sites: `useLiveness` rebuilds
49
+ * the tracker in three separate places (mount, retry, face-change reset), and a
50
+ * check in one of them would leave the others silently running gestures a
51
+ * flash-only workflow explicitly turned off.
52
+ *
53
+ * Deliberately keyed on the mode rather than `challengeCount: 0` — the count is
54
+ * clamped to at least one on purpose, so that a stray zero in a consumer's
55
+ * config can never quietly disable gesture liveness. Skipping gestures has to
56
+ * be asked for.
57
+ */
33
58
  export function pickChallenges(config: Partial<LivenessConfig> = {}): ChallengeConfig[] {
34
59
  const merged = { ...DEFAULT_LIVENESS_CONFIG, ...config };
60
+ if (!modeRunsGestures(merged.mode)) return [];
35
61
 
36
62
  let pool = CHALLENGE_POOL;
37
63
  if (merged.challengePool && merged.challengePool.length > 0) {