@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
@@ -8,12 +8,13 @@
8
8
  // server is going to make anyway.
9
9
  // ---------------------------------------------------------------------------
10
10
 
11
- import type { PoaDocumentType, ProofOfAddressConfig } from '../types/workflow';
11
+ import type { PoaDocumentType, PoaNameRule, ProofOfAddressConfig } from '../types/workflow';
12
12
 
13
13
  export const POA_TYPE_LABELS: Record<PoaDocumentType, string> = {
14
14
  utility_bill: 'Utility bill',
15
15
  bank_statement: 'Bank statement',
16
16
  tenancy_agreement: 'Tenancy agreement',
17
+ government_document: 'Government-issued document',
17
18
  other: 'Other document',
18
19
  };
19
20
 
@@ -21,9 +22,15 @@ const ALL_POA_TYPES: PoaDocumentType[] = [
21
22
  'utility_bill',
22
23
  'bank_statement',
23
24
  'tenancy_agreement',
25
+ 'government_document',
24
26
  'other',
25
27
  ];
26
28
 
29
+ /** A kind THIS build can label and draw. A newer kind the dashboard offers
30
+ * before the SDK ships is hidden rather than rendered as a blank row. */
31
+ const knownKinds = (kinds: readonly string[] | undefined): PoaDocumentType[] =>
32
+ (kinds ?? []).filter((k): k is PoaDocumentType => (ALL_POA_TYPES as string[]).includes(k));
33
+
27
34
  /** Default recency window the server checks the document date against. */
28
35
  export const DEFAULT_POA_MAX_AGE_DAYS = 90;
29
36
 
@@ -32,10 +39,52 @@ export function hasProofOfAddressStep(poa: ProofOfAddressConfig | undefined | nu
32
39
  return poa?.enabled === true;
33
40
  }
34
41
 
35
- /** The document kinds on offer. An absent or empty list means all of them. */
36
- export function poaDocumentTypes(poa: ProofOfAddressConfig | undefined): PoaDocumentType[] {
37
- const configured = poa?.documentTypes;
38
- return configured && configured.length > 0 ? configured : ALL_POA_TYPES;
42
+ /**
43
+ * The document kinds on offer for `country` (mirror of the web SDK's
44
+ * `poaOfferedKinds` — keep the three in lockstep): that country's override
45
+ * when the workflow declares one, else the global list, else all four.
46
+ */
47
+ export function poaDocumentTypes(
48
+ poa: ProofOfAddressConfig | undefined,
49
+ country?: string | null,
50
+ ): PoaDocumentType[] {
51
+ const override = knownKinds(country ? poa?.countryDocuments?.[country.toUpperCase()] : undefined);
52
+ if (override.length > 0) return override;
53
+ const configured = knownKinds(poa?.documentTypes);
54
+ return configured.length > 0 ? configured : ALL_POA_TYPES;
55
+ }
56
+
57
+ /**
58
+ * The name rule the server judges THIS document under — the country's per-kind
59
+ * exception, else the workflow default, else `required`. Mirror of the web
60
+ * SDK's `poaNamePolicy` and the server's `resolvePoaNamePolicy` — keep in
61
+ * lockstep. Read only to word the step: under `off` the header stops asking
62
+ * for the applicant's name.
63
+ */
64
+ export function poaNamePolicy(
65
+ poa: ProofOfAddressConfig | undefined | null,
66
+ country: string | undefined | null,
67
+ kind: PoaDocumentType | undefined | null,
68
+ ): PoaNameRule {
69
+ const exception = country && kind ? poa?.countryNameMatch?.[country.toUpperCase()]?.[kind] : undefined;
70
+ if (exception === 'required' || exception === 'optional' || exception === 'off') return exception;
71
+ const def = poa?.nameMatch;
72
+ return def === 'optional' || def === 'off' ? def : 'required';
73
+ }
74
+
75
+ /**
76
+ * Whether the org's accepted-country list admits `country`. An empty list
77
+ * accepts everyone; an unknown country is not refused here (the server is the
78
+ * gate, and it is soft on full flows).
79
+ */
80
+ export function poaCountryAccepted(
81
+ poa: ProofOfAddressConfig | undefined | null,
82
+ country: string | undefined | null,
83
+ ): boolean {
84
+ const accepted = poa?.countries;
85
+ if (!accepted?.length || !country) return true;
86
+ const code = country.toUpperCase();
87
+ return accepted.some((c) => c.toUpperCase() === code);
39
88
  }
40
89
 
41
90
  /**
@@ -63,12 +112,6 @@ export const POA_ACCEPTED_MIME_TYPES = [
63
112
  'application/pdf',
64
113
  ] as const;
65
114
 
66
- /**
67
- * Local size cap, matching the web SDK. The server allows more, but rejecting
68
- * here gives an immediate, specific message instead of a slow 413.
69
- */
70
- export const POA_MAX_BYTES = 20 * 1024 * 1024;
71
-
72
115
  export function isAcceptedPoaMimeType(mimeType: string | undefined): boolean {
73
116
  const base = (mimeType?.split(';')[0] ?? '').trim().toLowerCase();
74
117
  return (POA_ACCEPTED_MIME_TYPES as readonly string[]).includes(base);
@@ -116,3 +116,27 @@ export function groupCountriesByRegion(codes: string[]): RegionGroup[] {
116
116
  countries: buckets.get(region)!.sort((a, b) => a.name.localeCompare(b.name)),
117
117
  }));
118
118
  }
119
+
120
+ /**
121
+ * Lift the visitor's IP country out of its alphabetical or regional place to
122
+ * the top of a picker, so the one country most likely to be theirs is the
123
+ * first thing they see rather than something to scroll for.
124
+ *
125
+ * Takes the ALREADY-FILTERED list: the pin stays subject to the search, so
126
+ * typing narrows to what was asked for rather than keeping a row that does
127
+ * not match it. Generic over the row shape because the country-select step
128
+ * pins bare ISO codes and the dial-code sheet pins option objects; one rule,
129
+ * two callers. Mirrors the web SDK's CountryRegionPicker + PhoneNumberInput
130
+ * and Flutter's `pinGeoRow`.
131
+ */
132
+ export function pinGeoRow<T>(
133
+ visible: readonly T[],
134
+ geoCountry: string | null | undefined,
135
+ codeOf: (item: T) => string,
136
+ ): { pinned: T | null; rest: T[] } {
137
+ const geo = geoCountry?.trim().toUpperCase();
138
+ if (!geo) return { pinned: null, rest: [...visible] };
139
+ const hit = visible.find((item) => codeOf(item).toUpperCase() === geo);
140
+ if (hit === undefined) return { pinned: null, rest: [...visible] };
141
+ return { pinned: hit, rest: visible.filter((item) => item !== hit) };
142
+ }
@@ -70,7 +70,7 @@ export function registrationNumberHint(
70
70
  tip: NG_TIP,
71
71
  isValidFormat: (value) => NG_PREFIX_RE.test(value.trim()),
72
72
  formatError:
73
- 'Start with RC, BN, IT, LP or LLP followed by the number — no spaces, e.g. RC0000000.',
73
+ 'Start with RC, BN, IT, LP or LLP followed by the number, no spaces, e.g. RC0000000.',
74
74
  };
75
75
  }
76
76
 
@@ -11,8 +11,8 @@
11
11
  // capture → (nfc) → (liveness) → (poa) → (questionnaire) →
12
12
  // submitted
13
13
  // business: consent → (email) → (phone) → business-details →
14
- // (key-people) → (documents) → (applicant-role → capture leg) →
15
- // (questionnaire) → submitted
14
+ // (documents) → (questionnaire) → (key-people) →
15
+ // (applicant-role → country-select → capture leg) → submitted
16
16
  //
17
17
  // Mirrors the web SDK's `lib/step-order.ts`, plus the `nfc` step, which is a
18
18
  // real step here — the web SDK can't do ISO-DEP so it only previews the screen.
@@ -20,20 +20,47 @@
20
20
 
21
21
  import type { WorkflowBusinessConfig } from '../types/business';
22
22
  import type { KYCStep } from '../types/config';
23
+ import { applyResubmitSteps, type ResubmitConfig } from '../lib/resubmit';
24
+ import { addressFlowSteps, type AddressFlowOptions } from '../lib/address-flow';
23
25
  import { businessSectionSteps, hasApplicantVerification } from './businessSteps';
24
26
 
25
27
  export interface StepOrderOptions {
26
28
  isBusiness: boolean;
29
+ /** Scoped flows: no identity section — each scope's headline step is the
30
+ * flow (see lib/scope.ts). */
31
+ scope?: import('../lib/scope').WorkflowScope | null;
27
32
  /** Business (KYB) configuration — drives the application-section steps. */
28
33
  business?: WorkflowBusinessConfig;
29
34
  hasDocCapture: boolean;
30
35
  hasNfc: boolean;
31
36
  hasLiveness: boolean;
32
37
  hasCountrySelect: boolean;
38
+ /**
39
+ * Whether the flow opens on the consent screen. Absent = yes. `false` is the
40
+ * workflow's `consentStep: false`: the host app has already asked, so the
41
+ * flow opens on its first real step instead (config/consentStep.ts).
42
+ */
43
+ hasConsent?: boolean;
33
44
  hasEmailVerification: boolean;
34
45
  hasPhoneVerification: boolean;
35
46
  hasPoa: boolean;
47
+ hasAddressCollection: boolean;
48
+ /**
49
+ * Which address screens the flow has, from the ONE `addressFlowFor` call.
50
+ * The list is built by `addressFlowSteps`, the same builder the address
51
+ * steps navigate by, so the progress bar and the flow cannot disagree.
52
+ * Absent falls back to the default shape (no search, entrance offered).
53
+ */
54
+ addressFlow?: AddressFlowOptions;
36
55
  hasQuestionnaire: boolean;
56
+ /**
57
+ * A reviewer sent this back to redo specific steps.
58
+ *
59
+ * Applied LAST, over the fully-built order, so it narrows whatever the flow
60
+ * would otherwise have been rather than having to know how that order was
61
+ * assembled — which differs between the individual and KYB branches below.
62
+ */
63
+ resubmit?: ResubmitConfig | null;
37
64
  }
38
65
 
39
66
  // Contact-verification OTP steps sit right after consent (both flows) — a cheap
@@ -56,10 +83,27 @@ function captureLeg(o: StepOrderOptions): KYCStep[] {
56
83
  }
57
84
 
58
85
  export function buildStepOrder(o: StepOrderOptions): KYCStep[] {
86
+ return applyResubmitSteps(fullStepOrder(o), o.resubmit);
87
+ }
88
+
89
+ /** The flow's opening screen: consent, unless the workflow switched it off. */
90
+ function openingSteps(o: StepOrderOptions): KYCStep[] {
91
+ return o.hasConsent === false ? [] : ['consent'];
92
+ }
93
+
94
+ /** The flow as configured, before any reviewer narrowing. */
95
+ function fullStepOrder(o: StepOrderOptions): KYCStep[] {
59
96
  // Business (KYB) flow — the application section, then (when the workflow
60
97
  // requires applicant verification) the ordinary individual capture leg.
61
98
  if (o.isBusiness) {
62
- const steps: KYCStep[] = ['consent', ...contactSteps(o), ...businessSectionSteps(o.business)];
99
+ // The questionnaire sits INSIDE the business section (before key people) —
100
+ // its questions are about the company, so it stays with the company form
101
+ // rather than trailing the applicant's own capture leg.
102
+ const steps: KYCStep[] = [
103
+ ...openingSteps(o),
104
+ ...contactSteps(o),
105
+ ...businessSectionSteps(o.business, o.hasQuestionnaire, o.hasAddressCollection),
106
+ ];
63
107
  if (hasApplicantVerification(o.business)) {
64
108
  // The applicant may hold an ID issued anywhere the org can verify —
65
109
  // more than one granted country (hasCountrySelect, derived from the
@@ -68,16 +112,56 @@ export function buildStepOrder(o: StepOrderOptions): KYCStep[] {
68
112
  if (o.hasCountrySelect) steps.push('country-select');
69
113
  steps.push('id-type', ...captureLeg(o));
70
114
  }
115
+ steps.push('submitted');
116
+ return steps;
117
+ }
118
+
119
+ // Scoped flows: the scope's headline section IS the flow.
120
+ if (o.scope === 'address') {
121
+ const steps: KYCStep[] = [...openingSteps(o), ...contactSteps(o)];
122
+ if (o.hasPoa) steps.push('proof-of-address');
123
+ steps.push(
124
+ ...addressFlowSteps(
125
+ o.addressFlow ?? { searchAvailable: false, photoMode: 'optional', streetViewOffered: false },
126
+ ),
127
+ );
128
+ if (o.hasQuestionnaire) steps.push('questionnaire');
129
+ steps.push('submitted');
130
+ return steps;
131
+ }
132
+ if (o.scope === 'biometric-authentication' || o.scope === 'biometric-enrollment') {
133
+ const steps: KYCStep[] = [...openingSteps(o), ...contactSteps(o), 'liveness'];
71
134
  if (o.hasQuestionnaire) steps.push('questionnaire');
72
135
  steps.push('submitted');
73
136
  return steps;
74
137
  }
138
+ if (o.scope === 'questionnaire') {
139
+ return [...openingSteps(o), ...contactSteps(o), 'questionnaire', 'submitted'];
140
+ }
141
+ if (o.scope === 'contact') {
142
+ return [...openingSteps(o), ...contactSteps(o), 'submitted'];
143
+ }
75
144
 
76
145
  const middle: KYCStep[] = [...captureLeg(o)];
77
146
  if (o.hasPoa) middle.push('proof-of-address');
147
+ // The address flow is FOUR real steps on an individual flow (find it, confirm
148
+ // it, show it, commit it), so the progress bar advances through them and back
149
+ // is ordinary step navigation. KYB keeps the single premises step, inserted
150
+ // by businessSectionSteps above.
151
+ if (o.hasAddressCollection) {
152
+ middle.push(
153
+ ...addressFlowSteps(
154
+ o.addressFlow ?? {
155
+ searchAvailable: false,
156
+ photoMode: 'optional',
157
+ streetViewOffered: false,
158
+ },
159
+ ),
160
+ );
161
+ }
78
162
  if (o.hasQuestionnaire) middle.push('questionnaire');
79
163
  return [
80
- 'consent',
164
+ ...openingSteps(o),
81
165
  ...contactSteps(o),
82
166
  ...(o.hasCountrySelect ? (['country-select'] as KYCStep[]) : []),
83
167
  'id-type',
@@ -109,10 +193,20 @@ export function nextStepInOrder(step: KYCStep, o: StepOrderOptions): KYCStep {
109
193
  return order[index + 1] ?? order[order.length - 1]!;
110
194
  }
111
195
 
112
- /** The step before `step` — what the back button goes to. */
196
+ /**
197
+ * The step before `step` — what the back button goes to.
198
+ *
199
+ * A step not in the order stands still, for the same reason `nextStepInOrder`
200
+ * refuses to guess at one: it has no predecessor in a flow that does not
201
+ * contain it. This used to fall through to `order[0]`, so an applicant resumed
202
+ * onto a step the flow had since dropped was thrown all the way back to consent
203
+ * by a single back-press, discarding everything they had done. Standing still
204
+ * is recoverable; that was not.
205
+ */
113
206
  export function previousStepInOrder(step: KYCStep, o: StepOrderOptions): KYCStep {
114
207
  const order = buildStepOrder(o);
115
208
  const index = order.indexOf(step);
116
- if (index <= 0) return order[0]!;
209
+ if (index < 0) return step;
210
+ if (index === 0) return order[0]!;
117
211
  return order[index - 1]!;
118
212
  }
@@ -37,7 +37,13 @@ export const LIGHT_COLORS: MyazaColorScheme = {
37
37
  backgroundSecondary: '#F6F5FE',
38
38
  textDark: '#070330',
39
39
  textSecondary: '#5A5775',
40
- textMuted: '#828197',
40
+ // React's muted-foreground, exactly. The mobile SDKs had invented a THIRD,
41
+ // dimmer text tier that web does not have, and it failed WCAG AA in both
42
+ // modes: 3.79:1 on white and 2.97:1 on the default dark, dropping to 2.45:1
43
+ // on the dark backgrounds orgs actually brand with. Nothing is gained by a
44
+ // tier nobody can read, so it takes the same value as the tier above it, the
45
+ // way web does with one token for both.
46
+ textMuted: '#5A5775',
41
47
  border: '#D3CFFC',
42
48
  primary: '#5645F5',
43
49
  onPrimary: '#FFFFFF',
@@ -61,7 +67,7 @@ export const DARK_COLORS: MyazaColorScheme = {
61
67
  backgroundSecondary: '#0F0C2E',
62
68
  textDark: '#F6F5FE',
63
69
  textSecondary: '#ACABBA',
64
- textMuted: '#5A5775',
70
+ textMuted: '#ACABBA',
65
71
  border: '#302D53',
66
72
  primary: '#7B6EF7',
67
73
  onPrimary: '#FFFFFF',
@@ -0,0 +1,35 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Upload size limits.
3
+ //
4
+ // One rule for every document the applicant attaches (proof of address, the
5
+ // KYB company documents, the entrance photo): images up to 5 MB, PDFs up to
6
+ // 15 MB (user decision 2026-09-06). The server's own cap is higher, so refusing
7
+ // here gives an immediate, specific message instead of a slow 413. A THREE-WAY
8
+ // MIRROR with the web SDK's `lib/upload-limits.ts` and Flutter's
9
+ // `config/upload_limits.dart`, pinned to
10
+ // `kyc-sdk-flutter/test/upload_limits_vectors.json`.
11
+ // ---------------------------------------------------------------------------
12
+
13
+ export const IMAGE_MAX_BYTES = 5 * 1024 * 1024;
14
+ export const PDF_MAX_BYTES = 15 * 1024 * 1024;
15
+
16
+ /** The line under every drop zone: what is accepted, and how big. */
17
+ export const UPLOAD_HINT = 'PDF, JPG, PNG · PDF max 15 MB, images max 5 MB';
18
+
19
+ export function isPdfMime(mime: string | null | undefined): boolean {
20
+ return (mime?.split(';')[0] ?? '').trim().toLowerCase() === 'application/pdf';
21
+ }
22
+
23
+ /**
24
+ * The refusal for a file over its cap, or null when it fits. An unknown size
25
+ * also passes: the server still judges the bytes at its own cap, and a picker
26
+ * that reports no size must not block a good file.
27
+ */
28
+ export function uploadSizeError(
29
+ mime: string | null | undefined,
30
+ size: number | null | undefined,
31
+ ): string | null {
32
+ if (typeof size !== 'number' || !Number.isFinite(size)) return null;
33
+ if (isPdfMime(mime)) return size > PDF_MAX_BYTES ? 'PDF is too large (max 15 MB).' : null;
34
+ return size > IMAGE_MAX_BYTES ? 'Image is too large (max 5 MB).' : null;
35
+ }
@@ -0,0 +1,36 @@
1
+ // Is this a website address?
2
+ //
3
+ // Deliberately not a URL parser. `new URL(value)` rejects "company.com" for
4
+ // having no scheme, which is how almost everyone writes a website, and accepts
5
+ // "mailto:x@y" and "javascript:alert(1)" for having one. Both answers are the
6
+ // wrong way round for a field labelled "Website". Mirrors the web SDK's
7
+ // lib/website.ts — keep the two in lockstep.
8
+
9
+ /** Trim, drop a scheme, drop a trailing slash. What the checks below run on. */
10
+ function hostOf(value: string): string {
11
+ return value
12
+ .trim()
13
+ .replace(/^https?:\/\//i, '')
14
+ .replace(/\/.*$/, '')
15
+ .replace(/:\d+$/, '');
16
+ }
17
+
18
+ const HOST = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/i;
19
+
20
+ export function isValidWebsite(value: string): boolean {
21
+ const raw = value.trim();
22
+ if (raw === '') return true; // Empty is for the required-field check, not this.
23
+
24
+ // A scheme we do not serve is a mistake worth catching: "mailto:" in a
25
+ // website box is a different thing entirely, not a typo in this one.
26
+ if (/^[a-z][a-z0-9+.-]*:/i.test(raw) && !/^https?:\/\//i.test(raw)) return false;
27
+
28
+ const host = hostOf(raw);
29
+ if (host.length === 0 || host.length > 253) return false;
30
+ if (!HOST.test(host)) return false;
31
+
32
+ // A TLD of at least two letters. Rules out "company." and "192.168.0.1",
33
+ // neither of which is a website somebody meant to type.
34
+ const tld = host.slice(host.lastIndexOf('.') + 1);
35
+ return /^[a-z]{2,}$/i.test(tld);
36
+ }
@@ -16,17 +16,22 @@
16
16
  */
17
17
  export const WORKFLOW_KEYS = [
18
18
  'subjectType',
19
+ 'scope',
19
20
  'business',
20
21
  'country',
21
22
  'countries',
22
23
  'idTypes',
24
+ 'multiId',
23
25
  'enableSelfie',
24
26
  'enableDocumentCapture',
25
27
  'allowDocumentUpload',
26
28
  'enableLiveness',
27
29
  'livenessMode',
28
30
  'flashSequenceLength',
31
+ 'biometric',
29
32
  'deviceIntelligence',
33
+ 'keyPeopleLinkRecovery',
34
+ 'consentStep',
30
35
  'requireMobileDevice',
31
36
  'voiceGuidance',
32
37
  'showThemeToggle',
@@ -39,7 +44,10 @@ export const WORKFLOW_KEYS = [
39
44
  'phoneVerification',
40
45
  'questionnaire',
41
46
  'proofOfAddress',
47
+ 'addressCollection',
42
48
  'nfc',
49
+ // Set only on a session a reviewer sent back, never on a published flow.
50
+ 'resubmit',
43
51
  ] as const;
44
52
 
45
53
  export type WorkflowKey = (typeof WORKFLOW_KEYS)[number];
@@ -49,8 +57,10 @@ export type WorkflowKey = (typeof WORKFLOW_KEYS)[number];
49
57
  * key it DEFINES; an absent key leaves the prop alone (which is how a flow that
50
58
  * only sets, say, `livenessMode` doesn't wipe a prop-supplied `userId`).
51
59
  *
52
- * `appearance` merges shallowly with flow keys winning per-field, so a flow
53
- * that only sets `primaryColor` doesn't wipe a prop-supplied `logo`.
60
+ * `appearance` and `biometric` merge shallowly with flow keys winning
61
+ * per-field, so a flow that only sets `primaryColor` doesn't wipe a
62
+ * prop-supplied `logo`, and a flow that only switches the selfie review on
63
+ * doesn't wipe a host app's `doneButton: false`.
54
64
  *
55
65
  * Pure and side-effect free.
56
66
  */
@@ -63,10 +73,10 @@ export function mergeWorkflowConfig<P extends Record<string, unknown>>(
63
73
  for (const key of WORKFLOW_KEYS) {
64
74
  const value = flowConfig[key];
65
75
  if (value === undefined) continue;
66
- if (key === 'appearance') {
67
- const propAppearance = props['appearance'];
76
+ if (key === 'appearance' || key === 'biometric') {
77
+ const propBlock = props[key];
68
78
  merged[key] = {
69
- ...(typeof propAppearance === 'object' && propAppearance !== null ? propAppearance : {}),
79
+ ...(typeof propBlock === 'object' && propBlock !== null ? propBlock : {}),
70
80
  ...(value as Record<string, unknown>),
71
81
  };
72
82
  } else {
@@ -96,6 +106,7 @@ const APPLICANT_LEG_KEYS = [
96
106
  'country',
97
107
  'countries',
98
108
  'idTypes',
109
+ 'multiId',
99
110
  'enableSelfie',
100
111
  'enableDocumentCapture',
101
112
  'allowDocumentUpload',
@@ -0,0 +1,100 @@
1
+ import { toBase64 } from './bytes';
2
+ import { EF, type Transceive } from './files';
3
+ import { readOptionalFile } from './optionalRead';
4
+ import type { SecureMessagingSession } from './secureMessaging';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // ACTIVE AUTHENTICATION — asking the chip to prove it is not a copy.
8
+ //
9
+ // Passive authentication (the SOD) proves the data was signed by the issuing
10
+ // state. It cannot prove this is the chip they signed it onto: copy a genuine
11
+ // passport's files onto a blank chip and every hash and signature still
12
+ // verifies, because none of them is bound to the silicon.
13
+ //
14
+ // So we ask the chip to SIGN something. Its private key never leaves it, and
15
+ // the matching public key sits in DG15 — which is itself hashed into the SOD,
16
+ // so the issuing state has vouched for it. A cloner can copy DG15; they cannot
17
+ // copy the key that answers for it.
18
+ //
19
+ // TWO THINGS MATTER HERE, and neither is visible from this file alone:
20
+ //
21
+ // • THE CHALLENGE IS THE SERVER'S. We do not generate it. A nonce we chose
22
+ // would let anyone replay one captured (challenge, signature) pair forever,
23
+ // which is the clone this is meant to catch.
24
+ // • WE DO NOT VERIFY. The signature is carried to the server untouched and
25
+ // checked there against a SOD-bound DG15. A client that verified its own
26
+ // chip would be a client an attacker can simply patch — the same reason
27
+ // passive authentication has always run server-side.
28
+ //
29
+ // Both are also why this file is short: reading and forwarding is the whole job.
30
+ // ---------------------------------------------------------------------------
31
+
32
+ /** ICAO 9303-11 fixes the Active-Authentication challenge at 8 bytes. */
33
+ export const AA_CHALLENGE_BYTES = 8;
34
+
35
+ /** The server's challenge: its id (to spend) and its bytes (for the chip). */
36
+ export interface AaChallenge {
37
+ id: string;
38
+ bytes: Uint8Array;
39
+ }
40
+
41
+ export interface ActiveAuthRead {
42
+ /** Base64 DG15 — the chip's AA public key. Absent ⇒ the chip has no AA. */
43
+ dg15?: string;
44
+ /** Base64 signature over the challenge. Absent ⇒ the chip would not sign. */
45
+ signature?: string;
46
+ }
47
+
48
+ /**
49
+ * INTERNAL AUTHENTICATE — hand the chip the challenge, take back its signature.
50
+ *
51
+ * Le is 0 (meaning "as much as you have"): the answer is one RSA modulus or one
52
+ * raw r||s pair, and the length varies by document. Naming a length would work
53
+ * on the passports we happened to test and truncate the rest.
54
+ */
55
+ async function internalAuthenticate(
56
+ sm: SecureMessagingSession,
57
+ transceive: Transceive,
58
+ challenge: Uint8Array,
59
+ ): Promise<Uint8Array | null> {
60
+ try {
61
+ const { data, statusWord } = sm.unprotect(
62
+ await transceive(
63
+ sm.protect({ cla: 0x00, ins: 0x88, p1: 0x00, p2: 0x00, data: challenge, le: 0 }),
64
+ ),
65
+ );
66
+ if (statusWord !== 0x9000 || data.length === 0) return null;
67
+ return data;
68
+ } catch {
69
+ // A chip that carries DG15 but refuses to sign is unusual but not a
70
+ // finding: it costs the anti-clone check, never the read.
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Read DG15 and get the chip to sign the server's challenge.
77
+ *
78
+ * Best-effort throughout, like every optional group: most chips in the field
79
+ * support no Active Authentication at all, and reporting that as a problem
80
+ * would flag the majority of genuine passports.
81
+ */
82
+ export async function readActiveAuth(
83
+ sm: SecureMessagingSession,
84
+ transceive: Transceive,
85
+ challenge: AaChallenge | undefined,
86
+ ): Promise<ActiveAuthRead> {
87
+ // No challenge means the server could not issue one. Reading DG15 anyway
88
+ // would cost a round trip on the document for a key nothing can be checked
89
+ // against, so the whole step is skipped.
90
+ if (!challenge || challenge.bytes.length !== AA_CHALLENGE_BYTES) return {};
91
+
92
+ const dg15 = await readOptionalFile(sm, transceive, EF.DG15, 'DG15');
93
+ if (!dg15) return {};
94
+
95
+ const signature = await internalAuthenticate(sm, transceive, challenge.bytes);
96
+ return {
97
+ dg15: toBase64(dg15),
98
+ ...(signature ? { signature: toBase64(signature) } : {}),
99
+ };
100
+ }
@@ -0,0 +1,76 @@
1
+ import { findTlv, readTlv } from './der';
2
+ import { EF, type Transceive } from './files';
3
+ import { readOptionalFile } from './optionalRead';
4
+ import type { SecureMessagingSession } from './secureMessaging';
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // The optional detail groups: DG7 (displayed signature image), DG11
8
+ // (additional personal details), DG12 (additional document details).
9
+ //
10
+ // All three are BAC-readable like DG1/DG2, small (a few KB between them), and
11
+ // OPTIONAL BY STANDARD — many issuers simply do not write them. So the read is
12
+ // gated on EF.COM, the chip's own table of contents: one tiny file names
13
+ // exactly which data groups exist, and absent groups are skipped instead of
14
+ // being probed with SELECTs the chip will refuse. When COM itself cannot be
15
+ // read (or does not parse), every group is attempted best-effort — the worst
16
+ // case is a fast refusal per absent file, which readOptionalFile absorbs.
17
+ //
18
+ // Deliberately read LAST, after the portrait: they are nice-to-have context,
19
+ // and a session that drops while fetching them has already banked everything
20
+ // that matters. Like DG2 they are only read once the SOD arrived — the server
21
+ // authenticates each group against it, and an unverifiable extra is one an
22
+ // attacker could have substituted.
23
+ //
24
+ // (DG3/DG4 — fingerprints and iris — are NOT here and never will be: they sit
25
+ // behind Extended Access Control, decryptable only by government inspection
26
+ // systems holding terminal-authentication certificates.)
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /** LDS1 data-group tag byte → DG number (ICAO 9303-10). */
30
+ const TAG_TO_DG: Readonly<Record<number, number>> = {
31
+ 0x61: 1, 0x75: 2, 0x63: 3, 0x76: 4, 0x65: 5, 0x66: 6, 0x67: 7, 0x68: 8,
32
+ 0x69: 9, 0x6a: 10, 0x6b: 11, 0x6c: 12, 0x6d: 13, 0x6e: 14, 0x6f: 15, 0x70: 16,
33
+ };
34
+
35
+ /**
36
+ * Parse EF.COM's data-group presence list (tag 5C inside the outer 60).
37
+ * Null when the bytes do not parse as a COM — the caller then probes instead.
38
+ */
39
+ export function parseComDataGroups(com: Uint8Array): Set<number> | null {
40
+ const outer = readTlv(com);
41
+ if (!outer || outer.tag !== 0x60) return null;
42
+ const list = findTlv(outer.value, 0x5c);
43
+ if (!list) return null;
44
+ const present = new Set<number>();
45
+ for (const byte of list.value) {
46
+ const dg = TAG_TO_DG[byte];
47
+ if (dg !== undefined) present.add(dg);
48
+ }
49
+ return present;
50
+ }
51
+
52
+ export interface ExtraGroupReads {
53
+ dg7: Uint8Array | null;
54
+ dg11: Uint8Array | null;
55
+ dg12: Uint8Array | null;
56
+ }
57
+
58
+ /**
59
+ * Read whichever of DG7/DG11/DG12 the chip declares (or, without a readable
60
+ * COM, whichever answer). Every read is best-effort — a failure costs the
61
+ * group, never the session's banked result.
62
+ */
63
+ export async function readExtraGroups(
64
+ sm: SecureMessagingSession,
65
+ transceive: Transceive,
66
+ ): Promise<ExtraGroupReads> {
67
+ const com = await readOptionalFile(sm, transceive, EF.COM, 'EF.COM');
68
+ const declared = com ? parseComDataGroups(com) : null;
69
+ const want = (dg: number): boolean => declared === null || declared.has(dg);
70
+
71
+ return {
72
+ dg7: want(7) ? await readOptionalFile(sm, transceive, EF.DG7, 'DG7') : null,
73
+ dg11: want(11) ? await readOptionalFile(sm, transceive, EF.DG11, 'DG11') : null,
74
+ dg12: want(12) ? await readOptionalFile(sm, transceive, EF.DG12, 'DG12') : null,
75
+ };
76
+ }
@@ -26,6 +26,11 @@ export const EF = {
26
26
  COM: 0x011e,
27
27
  DG1: 0x0101,
28
28
  DG2: 0x0102,
29
+ DG7: 0x0107,
30
+ DG11: 0x010b,
31
+ DG12: 0x010c,
32
+ /** DG15 — the Active-Authentication public key (see activeAuth.ts). */
33
+ DG15: 0x010f,
29
34
  SOD: 0x011d,
30
35
  } as const;
31
36
 
@@ -29,3 +29,4 @@ export {
29
29
  export { parseDg1 } from './dg1';
30
30
  export { decodeChipImage, isNfcAvailable, nfcUnavailableReason } from './native';
31
31
  export { cancelChipRead, readPassportChip, type ChipReadOptions } from './read';
32
+ export { AA_CHALLENGE_BYTES, readActiveAuth, type AaChallenge, type ActiveAuthRead } from './activeAuth';