@myazahq/kyc-sdk-react-native 2.0.1 → 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.
- package/README.md +111 -8
- package/android/build.gradle +6 -0
- package/android/src/main/AndroidManifest.xml +14 -4
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
- package/app.plugin.js +102 -13
- package/ios/HybridMyazaEmrtd.swift +447 -0
- package/ios/HybridMyazaFaceDetector.swift +104 -2
- package/ios/HybridMyazaRectDetector.swift +103 -0
- package/ios/HybridMyazaTextRecognizer.swift +111 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
- package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
- package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
- package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
- package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
- package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
- package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
- package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
- package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
- package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
- package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
- package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
- package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
- package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
- package/package.json +16 -4
- package/src/MyazaKYC.tsx +115 -22
- package/src/capture/documentIdentity.ts +153 -0
- package/src/capture/documentSignals.ts +177 -0
- package/src/capture/documentTextGate.ts +171 -0
- package/src/capture/hints.ts +57 -0
- package/src/capture/index.ts +24 -0
- package/src/capture/rectDetector.ts +84 -0
- package/src/capture/rectGate.ts +91 -0
- package/src/capture/useAutoCapture.ts +198 -0
- package/src/components/CameraPermissionView.tsx +39 -3
- package/src/components/CameraViewfinder.tsx +226 -70
- package/src/components/ContactCodeEntry.tsx +139 -0
- package/src/components/CountryField.tsx +87 -0
- package/src/components/CountryFlag.tsx +15 -15
- package/src/components/DialCodePicker.tsx +186 -0
- package/src/components/DocumentCropper.tsx +1 -1
- package/src/components/DocumentReview.tsx +142 -0
- package/src/components/DocumentReviewSide.tsx +135 -0
- package/src/components/DocumentReviewZoom.tsx +119 -0
- package/src/components/ExpiryCountdown.tsx +52 -0
- package/src/components/FlashOverlay.tsx +156 -0
- package/src/components/Icon.tsx +67 -1
- package/src/components/KycFlow.tsx +66 -94
- package/src/components/KycSheet.tsx +148 -18
- package/src/components/MediaSourceSheet.tsx +163 -0
- package/src/components/MyazaButton.tsx +14 -2
- package/src/components/MyazaDateField.tsx +240 -0
- package/src/components/MyazaInput.tsx +73 -26
- package/src/components/MyazaSelect.tsx +283 -0
- package/src/components/MyazaWordmark.tsx +33 -0
- package/src/components/OptionRow.tsx +132 -0
- package/src/components/PhoneNumberInput.tsx +149 -0
- package/src/components/PoweredBy.tsx +89 -0
- package/src/components/ReadyPrimer.tsx +149 -0
- package/src/components/StepView.tsx +91 -0
- package/src/components/Typography.tsx +18 -3
- package/src/components/VerifiedNotice.tsx +38 -0
- package/src/components/WhatsAppIcon.tsx +29 -0
- package/src/components/WorkflowGate.tsx +128 -0
- package/src/components/brand-font.ts +113 -0
- package/src/components/flashHoleGeometry.ts +35 -0
- package/src/components/flow/FatalConfigError.tsx +56 -0
- package/src/components/fonts.ts +2 -19
- package/src/components/readyPrimerContent.ts +52 -0
- package/src/components/runtime.tsx +38 -43
- package/src/components/stepHeaderMeta.tsx +87 -0
- package/src/components/theme-provider.tsx +89 -0
- package/src/components/useWorkflowMount.ts +116 -0
- package/src/components/viewfinder/CardGuide.tsx +60 -0
- package/src/components/viewfinder/DocumentGhost.tsx +149 -0
- package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
- package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
- package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
- package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
- package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
- package/src/config/brand.ts +97 -0
- package/src/config/business.ts +179 -0
- package/src/config/businessSteps.ts +105 -0
- package/src/config/contact.ts +159 -0
- package/src/config/countryNames.g.ts +254 -0
- package/src/config/currencyFlags.ts +144 -0
- package/src/config/font-resolve.ts +78 -0
- package/src/config/idTypes.ts +98 -1
- package/src/config/keyPeople.ts +152 -0
- package/src/config/phone.ts +39 -0
- package/src/config/proofOfAddress.ts +75 -0
- package/src/config/questionnaire.ts +153 -0
- package/src/config/regions.ts +118 -0
- package/src/config/registrationHint.ts +87 -0
- package/src/config/stepOrder.ts +118 -0
- package/src/config/theme.ts +38 -2
- package/src/config/workflowMerge.ts +124 -0
- package/src/emrtd/bac.ts +128 -0
- package/src/emrtd/bytes.ts +127 -0
- package/src/emrtd/crypto.ts +163 -0
- package/src/emrtd/der.ts +192 -0
- package/src/emrtd/dg2.ts +111 -0
- package/src/emrtd/files.ts +215 -0
- package/src/emrtd/index.ts +30 -0
- package/src/emrtd/mrzKey.ts +54 -0
- package/src/emrtd/native.ts +79 -0
- package/src/emrtd/optionalRead.ts +42 -0
- package/src/emrtd/read.ts +224 -0
- package/src/emrtd/retry.ts +98 -0
- package/src/emrtd/secureMessaging.ts +178 -0
- package/src/emrtd/session.ts +200 -0
- package/src/emrtd/stages.ts +122 -0
- package/src/index.ts +110 -0
- package/src/lib/calendar.ts +71 -0
- package/src/lib/use-keyboard-inset.ts +34 -0
- package/src/liveness/challengeManager.ts +27 -1
- package/src/liveness/faceContinuity.ts +153 -0
- package/src/liveness/flashDetector.ts +145 -0
- package/src/liveness/flashReadyGate.ts +106 -0
- package/src/liveness/flashRunner.ts +118 -0
- package/src/liveness/integritySignals.ts +60 -0
- package/src/liveness/livenessState.ts +139 -0
- package/src/liveness/speech.ts +19 -1
- package/src/liveness/types.ts +48 -1
- package/src/liveness/useFlashPhase.ts +78 -0
- package/src/liveness/useLiveness.ts +260 -75
- package/src/liveness/visionCameraFaceDetector.ts +14 -0
- package/src/mrz/extract.ts +101 -0
- package/src/mrz/parse.ts +178 -0
- package/src/mrz/textRecognizer.ts +149 -0
- package/src/screens/ApplicantRoleStep.tsx +331 -0
- package/src/screens/BusinessDetailsStep.tsx +212 -0
- package/src/screens/BusinessDocumentSlot.tsx +167 -0
- package/src/screens/BusinessDocumentsStep.tsx +213 -0
- package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
- package/src/screens/BusinessKeyPersonRow.tsx +170 -0
- package/src/screens/CompanyInfoFields.tsx +83 -0
- package/src/screens/ConsentStep.tsx +68 -68
- package/src/screens/ContactChannelChoice.tsx +133 -0
- package/src/screens/ContactCodeStep.tsx +130 -0
- package/src/screens/ContactDestinationField.tsx +70 -0
- package/src/screens/ContactFooterNote.tsx +34 -0
- package/src/screens/ContactVerificationStep.tsx +199 -0
- package/src/screens/ContactVerifiedPanel.tsx +36 -0
- package/src/screens/CountrySelectStep.tsx +210 -0
- package/src/screens/DocumentCaptureStep.tsx +190 -145
- package/src/screens/IdInputStep.tsx +19 -9
- package/src/screens/IdTypeStep.tsx +74 -33
- package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
- package/src/screens/LivenessStep.tsx +181 -267
- package/src/screens/MrzScanView.tsx +173 -0
- package/src/screens/NfcStep.tsx +290 -0
- package/src/screens/ProofOfAddressStep.tsx +238 -0
- package/src/screens/QuestionnaireField.tsx +219 -0
- package/src/screens/QuestionnaireMoneyField.tsx +134 -0
- package/src/screens/QuestionnaireStep.tsx +76 -0
- package/src/screens/SubmittedStep.tsx +5 -0
- package/src/screens/consent/model.ts +109 -0
- package/src/screens/document/CameraPhase.tsx +150 -0
- package/src/screens/document/RequiredPill.tsx +60 -0
- package/src/screens/liveness/LightingBanner.tsx +50 -0
- package/src/screens/liveness/LivenessOutcome.tsx +109 -0
- package/src/screens/liveness/ProgressDots.tsx +67 -0
- package/src/screens/liveness/SelfiePreview.tsx +39 -0
- package/src/screens/liveness/constants.ts +31 -0
- package/src/screens/liveness/index.ts +8 -0
- package/src/screens/liveness/resolveGuidance.ts +27 -0
- package/src/screens/liveness/useSelfieUpload.ts +107 -0
- package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
- package/src/screens/nfc/NfcReadBody.tsx +63 -0
- package/src/screens/nfc/NfcReadProgress.tsx +96 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
- package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
- package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
- package/src/screens/nfc/availability.ts +73 -0
- package/src/screens/nfc/readErrorMessage.ts +93 -0
- package/src/screens/nfc/useChipPortrait.ts +58 -0
- package/src/screens/nfc/useNfcAvailability.ts +82 -0
- package/src/screens/useFlashHole.ts +105 -0
- package/src/screens/useFlashSequence.ts +96 -0
- package/src/screens/usePoaAttach.ts +121 -0
- package/src/services/api-types.ts +164 -0
- package/src/services/api-verify-types.ts +99 -0
- package/src/services/api.ts +72 -102
- package/src/services/cardCrop.ts +25 -10
- package/src/services/contactErrors.ts +43 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/documentPicker.ts +20 -0
- package/src/services/fingerprint-sources.ts +129 -0
- package/src/services/fingerprint.ts +101 -0
- package/src/services/haptics.ts +38 -0
- package/src/services/mediaCompress.ts +7 -2
- package/src/services/resolveUrl.ts +3 -2
- package/src/services/validators.ts +3 -0
- package/src/services/workflowGate.ts +138 -0
- package/src/specs/MyazaEmrtd.nitro.ts +114 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
- package/src/specs/MyazaRectDetector.nitro.ts +50 -0
- package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
- package/src/store/derive.ts +225 -0
- package/src/store/kycStore.ts +250 -149
- package/src/store/state.ts +253 -0
- package/src/store/submit.ts +101 -0
- package/src/store/submitApplicant.ts +81 -0
- package/src/types/appearance.ts +113 -0
- package/src/types/business.ts +131 -0
- package/src/types/config.ts +150 -119
- package/src/types/id-types.ts +57 -0
- package/src/types/verification.ts +5 -0
- package/src/types/workflow.ts +141 -0
- package/src/utils/amountFormat.ts +73 -0
- package/src/utils/tokens.ts +4 -2
- package/src/__tests__/cardCrop.test.ts +0 -39
- package/src/__tests__/deviceMetadata.test.ts +0 -34
- package/src/__tests__/errors.test.ts +0 -34
- package/src/__tests__/flow.test.ts +0 -61
- package/src/__tests__/gestureDetector.test.ts +0 -37
- package/src/__tests__/liveness.test.ts +0 -112
- package/src/__tests__/resolveUrl.test.ts +0 -64
- package/src/__tests__/validators.test.ts +0 -38
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { collectDeviceMetadata, inferDeviceType } from '../services/deviceMetadata';
|
|
2
|
-
|
|
3
|
-
// expo-device's DeviceType enum: UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4.
|
|
4
|
-
describe('inferDeviceType', () => {
|
|
5
|
-
it('maps the expo-device DeviceType enum to the dashboard classes', () => {
|
|
6
|
-
expect(inferDeviceType({ deviceType: 1 })).toBe('mobile'); // PHONE
|
|
7
|
-
expect(inferDeviceType({ deviceType: 2 })).toBe('tablet'); // TABLET
|
|
8
|
-
expect(inferDeviceType({ deviceType: 3 })).toBe('desktop'); // DESKTOP
|
|
9
|
-
expect(inferDeviceType({ deviceType: 4 })).toBe('unknown'); // TV
|
|
10
|
-
expect(inferDeviceType({ deviceType: 0 })).toBe('unknown'); // UNKNOWN
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('falls back to the platform when expo-device is unavailable (Node → ios → mobile)', () => {
|
|
14
|
-
expect(inferDeviceType(undefined)).toBe('mobile');
|
|
15
|
-
expect(inferDeviceType({ deviceType: null })).toBe('mobile');
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
describe('collectDeviceMetadata', () => {
|
|
20
|
-
it('always sets a device.type (never the empty dashboard "—")', () => {
|
|
21
|
-
const meta = collectDeviceMetadata();
|
|
22
|
-
expect(meta.device.type).toBeDefined();
|
|
23
|
-
expect(['mobile', 'tablet', 'desktop', 'unknown']).toContain(meta.device.type);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('keeps sdkType react-native and includes the device sub-object', () => {
|
|
27
|
-
const meta = collectDeviceMetadata();
|
|
28
|
-
expect(meta.sdkType).toBe('react-native');
|
|
29
|
-
// Under the Node test runner expo-device isn't resolvable, so the type comes
|
|
30
|
-
// from the platform fallback (ios → mobile) and brand/vendor are omitted.
|
|
31
|
-
expect(meta.device.type).toBe('mobile');
|
|
32
|
-
expect(meta.device).toHaveProperty('type');
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { KYCApiError } from '../services/api';
|
|
2
|
-
import { mapToKycError } from '../services/errors';
|
|
3
|
-
|
|
4
|
-
describe('mapToKycError', () => {
|
|
5
|
-
it('maps 401 → invalid_api_key', () => {
|
|
6
|
-
const e = mapToKycError(new KYCApiError('nope', 401), 'verify');
|
|
7
|
-
expect(e.code).toBe('invalid_api_key');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
it('maps 402 → insufficient_credits with details', () => {
|
|
11
|
-
const e = mapToKycError(new KYCApiError('no credits', 402, undefined, { required: 5, balance: 1, currency: 'USD' }), 'verify');
|
|
12
|
-
expect(e.code).toBe('insufficient_credits');
|
|
13
|
-
expect(e.details).toEqual({ required: 5, balance: 1, currency: 'USD' });
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('maps 403 id_type_not_allowed and feature_disabled → feature_disabled', () => {
|
|
17
|
-
expect(mapToKycError(new KYCApiError('x', 403, 'id_type_not_allowed'), 'verify').code).toBe('feature_disabled');
|
|
18
|
-
expect(mapToKycError(new KYCApiError('x', 403, 'feature_disabled', { feature: 'gov_db_check' }), 'verify').code).toBe('feature_disabled');
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('maps 5xx by context (upload_failed vs network_error)', () => {
|
|
22
|
-
expect(mapToKycError(new KYCApiError('x', 503), 'upload').code).toBe('upload_failed');
|
|
23
|
-
expect(mapToKycError(new KYCApiError('x', 503), 'verify').code).toBe('network_error');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('maps a network TypeError → network_error', () => {
|
|
27
|
-
expect(mapToKycError(new TypeError('Network request failed'), 'verify').code).toBe('network_error');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('falls back to the context code for unknown errors', () => {
|
|
31
|
-
expect(mapToKycError(new Error('weird'), 'upload').code).toBe('upload_failed');
|
|
32
|
-
expect(mapToKycError(new Error('weird'), 'verify').code).toBe('unknown');
|
|
33
|
-
});
|
|
34
|
-
});
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { nextStepAfter, previousStepBefore, livenessEnabled, type KycState } from '../store/kycStore';
|
|
2
|
-
import { INITIAL_SERVER_CONFIG } from '../store/serverConfig';
|
|
3
|
-
import type { MyazaKYCConfig } from '../types/config';
|
|
4
|
-
|
|
5
|
-
// Build a minimal KycState for exercising the pure navigation helpers. The
|
|
6
|
-
// helpers only read currentStep/selectedIdType/serverConfig/config, so we cast a
|
|
7
|
-
// partial object (the unused action fields are never touched).
|
|
8
|
-
function makeState(over: Partial<KycState>): KycState {
|
|
9
|
-
const config = { apiKey: 'pk_test_x', country: 'NG', ...(over.config ?? {}) } as MyazaKYCConfig;
|
|
10
|
-
return {
|
|
11
|
-
config,
|
|
12
|
-
selectedIdType: null,
|
|
13
|
-
serverConfig: INITIAL_SERVER_CONFIG,
|
|
14
|
-
currentStep: 'consent',
|
|
15
|
-
...over,
|
|
16
|
-
} as KycState;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('flow navigation', () => {
|
|
20
|
-
it('number-only IDs skip document capture (id-type → id-input)', () => {
|
|
21
|
-
const s = makeState({ selectedIdType: 'bvn' });
|
|
22
|
-
expect(nextStepAfter('id-type', s)).toBe('id-input');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('document IDs go to document capture (id-type → document-capture)', () => {
|
|
26
|
-
const s = makeState({ selectedIdType: 'passport' });
|
|
27
|
-
expect(nextStepAfter('id-type', s)).toBe('document-capture');
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('reaches liveness when enabled, else jumps to submitted', () => {
|
|
31
|
-
const enabled = makeState({ selectedIdType: 'bvn', config: { apiKey: 'pk_test_x', country: 'NG', enableLiveness: true } as MyazaKYCConfig });
|
|
32
|
-
expect(nextStepAfter('id-input', enabled)).toBe('liveness');
|
|
33
|
-
|
|
34
|
-
const disabled = makeState({ selectedIdType: 'bvn', config: { apiKey: 'pk_test_x', country: 'NG', enableLiveness: false } as MyazaKYCConfig });
|
|
35
|
-
expect(nextStepAfter('id-input', disabled)).toBe('submitted');
|
|
36
|
-
expect(livenessEnabled(disabled)).toBe(false);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('server livenessCheck=false wins over the consumer baseline', () => {
|
|
40
|
-
const s = makeState({
|
|
41
|
-
selectedIdType: 'bvn',
|
|
42
|
-
config: { apiKey: 'pk_test_x', country: 'NG', enableLiveness: true } as MyazaKYCConfig,
|
|
43
|
-
serverConfig: {
|
|
44
|
-
status: 'ready',
|
|
45
|
-
fatal: false,
|
|
46
|
-
idTypes: [{ country: 'NG', idType: 'bvn', features: { documentVerification: true, livenessCheck: false, govDbCheck: true } }],
|
|
47
|
-
},
|
|
48
|
-
});
|
|
49
|
-
expect(livenessEnabled(s)).toBe(false);
|
|
50
|
-
expect(nextStepAfter('id-input', s)).toBe('submitted');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('back navigation mirrors the forward path', () => {
|
|
54
|
-
const doc = makeState({ selectedIdType: 'passport' });
|
|
55
|
-
expect(previousStepBefore('document-capture', doc)).toBe('id-type');
|
|
56
|
-
expect(previousStepBefore('liveness', doc)).toBe('document-capture');
|
|
57
|
-
|
|
58
|
-
const num = makeState({ selectedIdType: 'bvn' });
|
|
59
|
-
expect(previousStepBefore('liveness', num)).toBe('id-input');
|
|
60
|
-
});
|
|
61
|
-
});
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { detectNod, detectTurn, detectBlink, detectSmile, pushHistory } from '../liveness/gestureDetector';
|
|
2
|
-
|
|
3
|
-
describe('gesture detectors (native-signal thresholds, ported from Flutter)', () => {
|
|
4
|
-
it('detectNod passes when pitch dips below −10° then recovers above −5°', () => {
|
|
5
|
-
expect(detectNod([0, -12, -3])).toBe(true);
|
|
6
|
-
expect(detectNod([0, -6, -3])).toBe(false); // never dipped far enough
|
|
7
|
-
expect(detectNod([0, -12, -8])).toBe(false); // hasn't recovered
|
|
8
|
-
expect(detectNod([-12])).toBe(false); // <2 frames
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('detectTurn passes when yaw exceeds ±25°', () => {
|
|
12
|
-
expect(detectTurn(30)).toBe(true);
|
|
13
|
-
expect(detectTurn(-26)).toBe(true);
|
|
14
|
-
expect(detectTurn(20)).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('detectBlink passes when eye-open drops below 0.2 then recovers above 0.5', () => {
|
|
18
|
-
expect(detectBlink([0.9, 0.1, 0.8])).toBe(true);
|
|
19
|
-
expect(detectBlink([0.9, 0.3, 0.8])).toBe(false); // never closed enough
|
|
20
|
-
expect(detectBlink([0.9, 0.1, 0.4])).toBe(false); // not recovered
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('detectSmile passes above 0.5', () => {
|
|
24
|
-
expect(detectSmile(0.6)).toBe(true);
|
|
25
|
-
expect(detectSmile(0.4)).toBe(false);
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('pushHistory', () => {
|
|
30
|
-
it('appends and trims to maxLen', () => {
|
|
31
|
-
let h: number[] = [];
|
|
32
|
-
for (let i = 0; i < 25; i++) h = pushHistory(h, i, 20);
|
|
33
|
-
expect(h.length).toBe(20);
|
|
34
|
-
expect(h[h.length - 1]).toBe(24);
|
|
35
|
-
expect(h[0]).toBe(5);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
// expo-speech is an ESM native module; the pure-logic tests here don't exercise
|
|
2
|
-
// TTS, so stub it (matches jest.config's "native modules tested later" note).
|
|
3
|
-
jest.mock('expo-speech', () => ({ speak: jest.fn(), stop: jest.fn() }));
|
|
4
|
-
|
|
5
|
-
import { resolveVoiceGuidance } from '../liveness/speech';
|
|
6
|
-
import { evaluateChallenge } from '../liveness/gestureDetector';
|
|
7
|
-
import { pickChallenges, ChallengeTracker } from '../liveness/challengeManager';
|
|
8
|
-
import type { LivenessFaceData } from '../liveness/types';
|
|
9
|
-
|
|
10
|
-
// Pure-logic tests for the liveness machine's building blocks. The stateful hook
|
|
11
|
-
// (useLiveness) and the native frame-processor bridge are exercised on-device;
|
|
12
|
-
// here we lock down the ML-free pieces that drive them.
|
|
13
|
-
|
|
14
|
-
function face(partial: Partial<LivenessFaceData> = {}): LivenessFaceData {
|
|
15
|
-
return {
|
|
16
|
-
headEulerAngleX: 0,
|
|
17
|
-
headEulerAngleY: 0,
|
|
18
|
-
headEulerAngleZ: 0,
|
|
19
|
-
smilingProbability: 0,
|
|
20
|
-
leftEyeOpenProbability: 1,
|
|
21
|
-
rightEyeOpenProbability: 1,
|
|
22
|
-
faceSizeRatio: 0.4,
|
|
23
|
-
faceCount: 1,
|
|
24
|
-
brightness: 128,
|
|
25
|
-
...partial,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe('resolveVoiceGuidance', () => {
|
|
30
|
-
it('treats undefined / true as enabled en-US', () => {
|
|
31
|
-
expect(resolveVoiceGuidance(undefined)).toEqual({ enabled: true, language: 'en-US' });
|
|
32
|
-
expect(resolveVoiceGuidance(true)).toEqual({ enabled: true, language: 'en-US' });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it('treats false as disabled', () => {
|
|
36
|
-
expect(resolveVoiceGuidance(false)).toEqual({ enabled: false, language: 'en-US' });
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('honours an object with enabled + language', () => {
|
|
40
|
-
expect(resolveVoiceGuidance({ enabled: true, language: 'fr-FR' })).toEqual({
|
|
41
|
-
enabled: true,
|
|
42
|
-
language: 'fr-FR',
|
|
43
|
-
});
|
|
44
|
-
// enabled defaults to true when omitted
|
|
45
|
-
expect(resolveVoiceGuidance({ language: 'en-GB' })).toEqual({ enabled: true, language: 'en-GB' });
|
|
46
|
-
expect(resolveVoiceGuidance({ enabled: false })).toEqual({ enabled: false, language: 'en-US' });
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
describe('evaluateChallenge (drives the state machine gesture switch)', () => {
|
|
51
|
-
it('nod reads the pitch history', () => {
|
|
52
|
-
expect(evaluateChallenge('nod', face(), [0, -12, -3], [])).toBe(true);
|
|
53
|
-
expect(evaluateChallenge('nod', face(), [0, -6], [])).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('turn reads the latest yaw', () => {
|
|
57
|
-
expect(evaluateChallenge('turn', face({ headEulerAngleY: 30 }), [], [])).toBe(true);
|
|
58
|
-
expect(evaluateChallenge('turn', face({ headEulerAngleY: 10 }), [], [])).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('blink reads the eye-open history', () => {
|
|
62
|
-
expect(evaluateChallenge('blink', face(), [], [0.9, 0.1, 0.8])).toBe(true);
|
|
63
|
-
expect(evaluateChallenge('blink', face(), [], [0.9, 0.3, 0.8])).toBe(false);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('smile reads the latest smile probability', () => {
|
|
67
|
-
expect(evaluateChallenge('smile', face({ smilingProbability: 0.6 }), [], [])).toBe(true);
|
|
68
|
-
expect(evaluateChallenge('smile', face({ smilingProbability: 0.4 }), [], [])).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
describe('pickChallenges (session selection)', () => {
|
|
73
|
-
it('picks the configured count of distinct challenges', () => {
|
|
74
|
-
const picked = pickChallenges({ challengeCount: 2 });
|
|
75
|
-
expect(picked).toHaveLength(2);
|
|
76
|
-
const types = picked.map((c) => c.type);
|
|
77
|
-
expect(new Set(types).size).toBe(2);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('never pairs nod with turn (similarity group)', () => {
|
|
81
|
-
for (let i = 0; i < 50; i++) {
|
|
82
|
-
const types = pickChallenges({ challengeCount: 2 }).map((c) => c.type);
|
|
83
|
-
const hasNod = types.includes('nod');
|
|
84
|
-
const hasTurn = types.includes('turn');
|
|
85
|
-
expect(hasNod && hasTurn).toBe(false);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('respects a restricted challengePool', () => {
|
|
90
|
-
const picked = pickChallenges({ challengeCount: 2, challengePool: ['blink', 'smile'] });
|
|
91
|
-
expect(picked.every((c) => c.type === 'blink' || c.type === 'smile')).toBe(true);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('applies the per-challenge timeout override', () => {
|
|
95
|
-
const picked = pickChallenges({ challengeCount: 2, timeoutPerChallenge: 5 });
|
|
96
|
-
expect(picked.every((c) => c.timeoutSeconds === 5)).toBe(true);
|
|
97
|
-
});
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
describe('ChallengeTracker (progress)', () => {
|
|
101
|
-
it('advances through challenges and reports completion', () => {
|
|
102
|
-
const tracker = new ChallengeTracker(pickChallenges({ challengeCount: 2 }));
|
|
103
|
-
expect(tracker.totalCount).toBe(2);
|
|
104
|
-
expect(tracker.isComplete).toBe(false);
|
|
105
|
-
|
|
106
|
-
tracker.markCurrentPassed();
|
|
107
|
-
expect(tracker.advance()).toBe(true); // a second challenge exists
|
|
108
|
-
tracker.markCurrentPassed();
|
|
109
|
-
expect(tracker.advance()).toBe(false); // none left
|
|
110
|
-
expect(tracker.isComplete).toBe(true);
|
|
111
|
-
});
|
|
112
|
-
});
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { detectEnvironment, resolveBaseUrl, normalizeDevAssetUrl } from '../services/resolveUrl';
|
|
2
|
-
|
|
3
|
-
describe('detectEnvironment', () => {
|
|
4
|
-
it('maps each prefix (pk_ and sk_) to its environment', () => {
|
|
5
|
-
expect(detectEnvironment('pk_dev_abc')).toBe('development');
|
|
6
|
-
expect(detectEnvironment('sk_dev_abc')).toBe('development');
|
|
7
|
-
expect(detectEnvironment('pk_test_abc')).toBe('sandbox');
|
|
8
|
-
expect(detectEnvironment('sk_test_abc')).toBe('sandbox');
|
|
9
|
-
expect(detectEnvironment('pk_live_abc')).toBe('production');
|
|
10
|
-
expect(detectEnvironment('sk_live_abc')).toBe('production');
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it('throws on an unrecognized / malformed key', () => {
|
|
14
|
-
expect(() => detectEnvironment('nope')).toThrow(/Invalid Myaza API key/);
|
|
15
|
-
expect(() => detectEnvironment('pk_prod_abc')).toThrow();
|
|
16
|
-
expect(() => detectEnvironment('')).toThrow();
|
|
17
|
-
});
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
describe('resolveBaseUrl', () => {
|
|
21
|
-
it('uses the hardcoded sandbox/production URLs and ignores devUrl', () => {
|
|
22
|
-
expect(resolveBaseUrl('pk_test_abc')).toBe('https://sandbox.identity.myaza.app');
|
|
23
|
-
expect(resolveBaseUrl('pk_test_abc', 'http://example.test')).toBe('https://sandbox.identity.myaza.app');
|
|
24
|
-
expect(resolveBaseUrl('pk_live_abc')).toBe('https://identity.myaza.app');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('uses devUrl for development keys, defaulting to localhost', () => {
|
|
28
|
-
expect(resolveBaseUrl('pk_dev_abc', 'http://192.168.1.5:3001')).toBe('http://192.168.1.5:3001');
|
|
29
|
-
// Under the Node test runner Platform falls back to iOS → localhost.
|
|
30
|
-
expect(resolveBaseUrl('pk_dev_abc')).toBe('http://localhost:3001');
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
describe('normalizeDevAssetUrl', () => {
|
|
35
|
-
it('rewrites a localhost-family asset origin to the dev base origin', () => {
|
|
36
|
-
expect(normalizeDevAssetUrl('http://localhost:3001/api/kyc/branding/logo/x', 'http://10.0.2.2:3001')).toBe(
|
|
37
|
-
'http://10.0.2.2:3001/api/kyc/branding/logo/x',
|
|
38
|
-
);
|
|
39
|
-
expect(normalizeDevAssetUrl('http://127.0.0.1:3001/logo.png', 'http://10.0.2.2:3001')).toBe(
|
|
40
|
-
'http://10.0.2.2:3001/logo.png',
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('is a no-op when the base origin already matches', () => {
|
|
45
|
-
expect(normalizeDevAssetUrl('http://localhost:3001/logo.png', 'http://localhost:3001')).toBe(
|
|
46
|
-
'http://localhost:3001/logo.png',
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('never touches production/CDN URLs or an https base', () => {
|
|
51
|
-
// Public CDN asset, dev base → left alone (not a localhost host).
|
|
52
|
-
expect(normalizeDevAssetUrl('https://cdn.myaza.app/logo.png', 'http://10.0.2.2:3001')).toBe(
|
|
53
|
-
'https://cdn.myaza.app/logo.png',
|
|
54
|
-
);
|
|
55
|
-
// https base (sandbox/prod) → never rewrites, even a localhost asset.
|
|
56
|
-
expect(normalizeDevAssetUrl('http://localhost:3001/logo.png', 'https://sandbox.identity.myaza.app')).toBe(
|
|
57
|
-
'http://localhost:3001/logo.png',
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('passes through undefined', () => {
|
|
62
|
-
expect(normalizeDevAssetUrl(undefined, 'http://10.0.2.2:3001')).toBeUndefined();
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { validateIdNumber, maskIdNumber } from '../services/validators';
|
|
2
|
-
|
|
3
|
-
describe('validateIdNumber', () => {
|
|
4
|
-
it('validates Nigerian number-only IDs', () => {
|
|
5
|
-
expect(validateIdNumber('NG', 'bvn', '12345678901').valid).toBe(true);
|
|
6
|
-
expect(validateIdNumber('NG', 'bvn', '123').valid).toBe(false);
|
|
7
|
-
expect(validateIdNumber('NG', 'nin', '12345678901').valid).toBe(true);
|
|
8
|
-
expect(validateIdNumber('NG', 'vnin', 'ABCD123456789012').valid).toBe(true);
|
|
9
|
-
expect(validateIdNumber('NG', 'vnin', 'short').valid).toBe(false);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it('validates country-prefixed shared keys (passport, national-id)', () => {
|
|
13
|
-
expect(validateIdNumber('NG', 'passport', 'A12345678').valid).toBe(true);
|
|
14
|
-
expect(validateIdNumber('NG', 'passport', 'A1234567').valid).toBe(false); // GH format, not NG
|
|
15
|
-
expect(validateIdNumber('GH', 'passport', 'A1234567').valid).toBe(true);
|
|
16
|
-
expect(validateIdNumber('KE', 'national-id', '12345678').valid).toBe(true);
|
|
17
|
-
expect(validateIdNumber('ZA', 'national-id', '1234567890123').valid).toBe(true);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('validates Ghana Card and PVC patterns', () => {
|
|
21
|
-
expect(validateIdNumber('GH', 'ghana-card', 'GHA-123456789-0').valid).toBe(true);
|
|
22
|
-
expect(validateIdNumber('GH', 'ghana-card', '123456789').valid).toBe(false);
|
|
23
|
-
expect(validateIdNumber('NG', 'pvc', '1234567890123456789').valid).toBe(true);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('rejects empty input', () => {
|
|
27
|
-
expect(validateIdNumber('NG', 'bvn', ' ').valid).toBe(false);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
describe('maskIdNumber', () => {
|
|
32
|
-
it('masks the middle of a long number', () => {
|
|
33
|
-
expect(maskIdNumber('12345678901')).toBe('1234****901');
|
|
34
|
-
});
|
|
35
|
-
it('leaves short values untouched', () => {
|
|
36
|
-
expect(maskIdNumber('1234567')).toBe('1234567');
|
|
37
|
-
});
|
|
38
|
-
});
|