@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.
- 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/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 +96 -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 +156 -132
- package/src/screens/IdInputStep.tsx +13 -5
- package/src/screens/IdTypeStep.tsx +74 -33
- package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
- package/src/screens/LivenessStep.tsx +155 -247
- 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 +2 -2
- 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 -124
- 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://identity.myaza.app');
|
|
23
|
-
expect(resolveBaseUrl('pk_test_abc', 'http://example.test')).toBe('https://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://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
|
-
});
|