@velion-la/onboarding-sdk-react-native 1.0.0-beta.2
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/CHANGELOG.md +66 -0
- package/LICENSE +12 -0
- package/README.md +803 -0
- package/lib/commonjs/camera/AddressSearchBar.js +304 -0
- package/lib/commonjs/camera/AddressSearchBar.js.map +1 -0
- package/lib/commonjs/camera/DocumentCapture.js +505 -0
- package/lib/commonjs/camera/DocumentCapture.js.map +1 -0
- package/lib/commonjs/camera/LivenessCamera.js +534 -0
- package/lib/commonjs/camera/LivenessCamera.js.map +1 -0
- package/lib/commonjs/camera/MapAddressPicker.js +341 -0
- package/lib/commonjs/camera/MapAddressPicker.js.map +1 -0
- package/lib/commonjs/core/VelionOnboarding.js +815 -0
- package/lib/commonjs/core/VelionOnboarding.js.map +1 -0
- package/lib/commonjs/core/sessionDeclinedCopy.js +56 -0
- package/lib/commonjs/core/sessionDeclinedCopy.js.map +1 -0
- package/lib/commonjs/core/sessionStateMapper.js +137 -0
- package/lib/commonjs/core/sessionStateMapper.js.map +1 -0
- package/lib/commonjs/core/sseReasonMessages.js +450 -0
- package/lib/commonjs/core/sseReasonMessages.js.map +1 -0
- package/lib/commonjs/data/iso3166Alpha2Codes.json +1 -0
- package/lib/commonjs/data/iso3166CountryNames.json +251 -0
- package/lib/commonjs/i18n/index.js +30 -0
- package/lib/commonjs/i18n/index.js.map +1 -0
- package/lib/commonjs/i18n/strings/en.js +261 -0
- package/lib/commonjs/i18n/strings/en.js.map +1 -0
- package/lib/commonjs/i18n/strings/es.js +266 -0
- package/lib/commonjs/i18n/strings/es.js.map +1 -0
- package/lib/commonjs/index.js +213 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/steps/AddressInfoStep.js +650 -0
- package/lib/commonjs/steps/AddressInfoStep.js.map +1 -0
- package/lib/commonjs/steps/DocumentsOcrStep.js +397 -0
- package/lib/commonjs/steps/DocumentsOcrStep.js.map +1 -0
- package/lib/commonjs/steps/EmailContactStep.js +145 -0
- package/lib/commonjs/steps/EmailContactStep.js.map +1 -0
- package/lib/commonjs/steps/EmailOtpStep.js +220 -0
- package/lib/commonjs/steps/EmailOtpStep.js.map +1 -0
- package/lib/commonjs/steps/FaceMatchStep.js +166 -0
- package/lib/commonjs/steps/FaceMatchStep.js.map +1 -0
- package/lib/commonjs/steps/FingerprintStep.js +92 -0
- package/lib/commonjs/steps/FingerprintStep.js.map +1 -0
- package/lib/commonjs/steps/HumanBasicInfoStep.js +243 -0
- package/lib/commonjs/steps/HumanBasicInfoStep.js.map +1 -0
- package/lib/commonjs/steps/HumanExtendedInfoStep.js +268 -0
- package/lib/commonjs/steps/HumanExtendedInfoStep.js.map +1 -0
- package/lib/commonjs/steps/LegalBasicInfoStep.js +188 -0
- package/lib/commonjs/steps/LegalBasicInfoStep.js.map +1 -0
- package/lib/commonjs/steps/LegalExtendedInfoStep.js +166 -0
- package/lib/commonjs/steps/LegalExtendedInfoStep.js.map +1 -0
- package/lib/commonjs/steps/LivenessMatchStep.js +707 -0
- package/lib/commonjs/steps/LivenessMatchStep.js.map +1 -0
- package/lib/commonjs/steps/PhoneContactStep.js +146 -0
- package/lib/commonjs/steps/PhoneContactStep.js.map +1 -0
- package/lib/commonjs/steps/PhoneOtpStep.js +219 -0
- package/lib/commonjs/steps/PhoneOtpStep.js.map +1 -0
- package/lib/commonjs/steps/SwornDeclarationStep.js +196 -0
- package/lib/commonjs/steps/SwornDeclarationStep.js.map +1 -0
- package/lib/commonjs/steps/TaxInfoStep.js +244 -0
- package/lib/commonjs/steps/TaxInfoStep.js.map +1 -0
- package/lib/commonjs/steps/TermsAndConditionsStep.js +238 -0
- package/lib/commonjs/steps/TermsAndConditionsStep.js.map +1 -0
- package/lib/commonjs/steps/_shared.js +40 -0
- package/lib/commonjs/steps/_shared.js.map +1 -0
- package/lib/commonjs/steps/types.js +6 -0
- package/lib/commonjs/steps/types.js.map +1 -0
- package/lib/commonjs/transport/api.js +176 -0
- package/lib/commonjs/transport/api.js.map +1 -0
- package/lib/commonjs/transport/irisWebSocket.js +121 -0
- package/lib/commonjs/transport/irisWebSocket.js.map +1 -0
- package/lib/commonjs/transport/secureUrl.js +50 -0
- package/lib/commonjs/transport/secureUrl.js.map +1 -0
- package/lib/commonjs/transport/sse.js +248 -0
- package/lib/commonjs/transport/sse.js.map +1 -0
- package/lib/commonjs/types/index.js +391 -0
- package/lib/commonjs/types/index.js.map +1 -0
- package/lib/commonjs/ui/MapFallbackBoundary.js +42 -0
- package/lib/commonjs/ui/MapFallbackBoundary.js.map +1 -0
- package/lib/commonjs/ui/StepShell.js +118 -0
- package/lib/commonjs/ui/StepShell.js.map +1 -0
- package/lib/commonjs/ui/VelionOnboardingScreen.js +217 -0
- package/lib/commonjs/ui/VelionOnboardingScreen.js.map +1 -0
- package/lib/commonjs/ui/forms/index.js +38 -0
- package/lib/commonjs/ui/forms/index.js.map +1 -0
- package/lib/commonjs/ui/forms/useInlineValidation.js +88 -0
- package/lib/commonjs/ui/forms/useInlineValidation.js.map +1 -0
- package/lib/commonjs/ui/forms/validators.js +34 -0
- package/lib/commonjs/ui/forms/validators.js.map +1 -0
- package/lib/commonjs/ui/icons/HomeHeartIcon.js +38 -0
- package/lib/commonjs/ui/icons/HomeHeartIcon.js.map +1 -0
- package/lib/commonjs/ui/icons/StepIcon.js +59 -0
- package/lib/commonjs/ui/icons/StepIcon.js.map +1 -0
- package/lib/commonjs/ui/overlays/AnalyzingOverlay.js +284 -0
- package/lib/commonjs/ui/overlays/AnalyzingOverlay.js.map +1 -0
- package/lib/commonjs/ui/overlays/LoadingOverlay.js +108 -0
- package/lib/commonjs/ui/overlays/LoadingOverlay.js.map +1 -0
- package/lib/commonjs/ui/overlays/ProgressBar.js +95 -0
- package/lib/commonjs/ui/overlays/ProgressBar.js.map +1 -0
- package/lib/commonjs/ui/overlays/RetryDialog.js +125 -0
- package/lib/commonjs/ui/overlays/RetryDialog.js.map +1 -0
- package/lib/commonjs/ui/overlays/index.js +33 -0
- package/lib/commonjs/ui/overlays/index.js.map +1 -0
- package/lib/commonjs/ui/primitives/Banner.js +81 -0
- package/lib/commonjs/ui/primitives/Banner.js.map +1 -0
- package/lib/commonjs/ui/primitives/Button.js +142 -0
- package/lib/commonjs/ui/primitives/Button.js.map +1 -0
- package/lib/commonjs/ui/primitives/Card.js +50 -0
- package/lib/commonjs/ui/primitives/Card.js.map +1 -0
- package/lib/commonjs/ui/primitives/Checkbox.js +139 -0
- package/lib/commonjs/ui/primitives/Checkbox.js.map +1 -0
- package/lib/commonjs/ui/primitives/CountrySelect.js +288 -0
- package/lib/commonjs/ui/primitives/CountrySelect.js.map +1 -0
- package/lib/commonjs/ui/primitives/DatePicker.js +245 -0
- package/lib/commonjs/ui/primitives/DatePicker.js.map +1 -0
- package/lib/commonjs/ui/primitives/Input.js +101 -0
- package/lib/commonjs/ui/primitives/Input.js.map +1 -0
- package/lib/commonjs/ui/primitives/Select.js +209 -0
- package/lib/commonjs/ui/primitives/Select.js.map +1 -0
- package/lib/commonjs/ui/primitives/Spinner.js +25 -0
- package/lib/commonjs/ui/primitives/Spinner.js.map +1 -0
- package/lib/commonjs/ui/primitives/index.js +69 -0
- package/lib/commonjs/ui/primitives/index.js.map +1 -0
- package/lib/commonjs/ui/theme/ThemeProvider.js +74 -0
- package/lib/commonjs/ui/theme/ThemeProvider.js.map +1 -0
- package/lib/commonjs/ui/theme/index.js +68 -0
- package/lib/commonjs/ui/theme/index.js.map +1 -0
- package/lib/commonjs/ui/theme/tokens.js +154 -0
- package/lib/commonjs/ui/theme/tokens.js.map +1 -0
- package/lib/commonjs/utils/EventEmitter.js +144 -0
- package/lib/commonjs/utils/EventEmitter.js.map +1 -0
- package/lib/commonjs/utils/base64.js +53 -0
- package/lib/commonjs/utils/base64.js.map +1 -0
- package/lib/commonjs/utils/deviceFingerprint.js +132 -0
- package/lib/commonjs/utils/deviceFingerprint.js.map +1 -0
- package/lib/commonjs/utils/documentTypeOptions.js +35 -0
- package/lib/commonjs/utils/documentTypeOptions.js.map +1 -0
- package/lib/commonjs/utils/googleMapsPreflight.js +77 -0
- package/lib/commonjs/utils/googleMapsPreflight.js.map +1 -0
- package/lib/commonjs/utils/hmac.js +58 -0
- package/lib/commonjs/utils/hmac.js.map +1 -0
- package/lib/commonjs/utils/iso3166Countries.js +98 -0
- package/lib/commonjs/utils/iso3166Countries.js.map +1 -0
- package/lib/commonjs/utils/jpegFromUri.js +44 -0
- package/lib/commonjs/utils/jpegFromUri.js.map +1 -0
- package/lib/commonjs/utils/keepAwake.js +58 -0
- package/lib/commonjs/utils/keepAwake.js.map +1 -0
- package/lib/commonjs/utils/log.js +42 -0
- package/lib/commonjs/utils/log.js.map +1 -0
- package/lib/commonjs/utils/osGeocoder.js +70 -0
- package/lib/commonjs/utils/osGeocoder.js.map +1 -0
- package/lib/commonjs/utils/parseGoogleAddressComponents.js +74 -0
- package/lib/commonjs/utils/parseGoogleAddressComponents.js.map +1 -0
- package/lib/commonjs/utils/personNameNormalize.js +24 -0
- package/lib/commonjs/utils/personNameNormalize.js.map +1 -0
- package/lib/commonjs/utils/sdkError.js +34 -0
- package/lib/commonjs/utils/sdkError.js.map +1 -0
- package/lib/commonjs/utils/sdkVersion.js +13 -0
- package/lib/commonjs/utils/sdkVersion.js.map +1 -0
- package/lib/commonjs/utils/stepConfig.js +65 -0
- package/lib/commonjs/utils/stepConfig.js.map +1 -0
- package/lib/commonjs/utils/taxInfoConfig.js +19 -0
- package/lib/commonjs/utils/taxInfoConfig.js.map +1 -0
- package/lib/module/camera/AddressSearchBar.js +299 -0
- package/lib/module/camera/AddressSearchBar.js.map +1 -0
- package/lib/module/camera/DocumentCapture.js +500 -0
- package/lib/module/camera/DocumentCapture.js.map +1 -0
- package/lib/module/camera/LivenessCamera.js +529 -0
- package/lib/module/camera/LivenessCamera.js.map +1 -0
- package/lib/module/camera/MapAddressPicker.js +337 -0
- package/lib/module/camera/MapAddressPicker.js.map +1 -0
- package/lib/module/core/VelionOnboarding.js +811 -0
- package/lib/module/core/VelionOnboarding.js.map +1 -0
- package/lib/module/core/sessionDeclinedCopy.js +52 -0
- package/lib/module/core/sessionDeclinedCopy.js.map +1 -0
- package/lib/module/core/sessionStateMapper.js +128 -0
- package/lib/module/core/sessionStateMapper.js.map +1 -0
- package/lib/module/core/sseReasonMessages.js +446 -0
- package/lib/module/core/sseReasonMessages.js.map +1 -0
- package/lib/module/data/iso3166Alpha2Codes.json +1 -0
- package/lib/module/data/iso3166CountryNames.json +251 -0
- package/lib/module/i18n/index.js +25 -0
- package/lib/module/i18n/index.js.map +1 -0
- package/lib/module/i18n/strings/en.js +257 -0
- package/lib/module/i18n/strings/en.js.map +1 -0
- package/lib/module/i18n/strings/es.js +262 -0
- package/lib/module/i18n/strings/es.js.map +1 -0
- package/lib/module/index.js +36 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/steps/AddressInfoStep.js +646 -0
- package/lib/module/steps/AddressInfoStep.js.map +1 -0
- package/lib/module/steps/DocumentsOcrStep.js +391 -0
- package/lib/module/steps/DocumentsOcrStep.js.map +1 -0
- package/lib/module/steps/EmailContactStep.js +140 -0
- package/lib/module/steps/EmailContactStep.js.map +1 -0
- package/lib/module/steps/EmailOtpStep.js +215 -0
- package/lib/module/steps/EmailOtpStep.js.map +1 -0
- package/lib/module/steps/FaceMatchStep.js +161 -0
- package/lib/module/steps/FaceMatchStep.js.map +1 -0
- package/lib/module/steps/FingerprintStep.js +87 -0
- package/lib/module/steps/FingerprintStep.js.map +1 -0
- package/lib/module/steps/HumanBasicInfoStep.js +238 -0
- package/lib/module/steps/HumanBasicInfoStep.js.map +1 -0
- package/lib/module/steps/HumanExtendedInfoStep.js +263 -0
- package/lib/module/steps/HumanExtendedInfoStep.js.map +1 -0
- package/lib/module/steps/LegalBasicInfoStep.js +183 -0
- package/lib/module/steps/LegalBasicInfoStep.js.map +1 -0
- package/lib/module/steps/LegalExtendedInfoStep.js +161 -0
- package/lib/module/steps/LegalExtendedInfoStep.js.map +1 -0
- package/lib/module/steps/LivenessMatchStep.js +701 -0
- package/lib/module/steps/LivenessMatchStep.js.map +1 -0
- package/lib/module/steps/PhoneContactStep.js +141 -0
- package/lib/module/steps/PhoneContactStep.js.map +1 -0
- package/lib/module/steps/PhoneOtpStep.js +214 -0
- package/lib/module/steps/PhoneOtpStep.js.map +1 -0
- package/lib/module/steps/SwornDeclarationStep.js +191 -0
- package/lib/module/steps/SwornDeclarationStep.js.map +1 -0
- package/lib/module/steps/TaxInfoStep.js +239 -0
- package/lib/module/steps/TaxInfoStep.js.map +1 -0
- package/lib/module/steps/TermsAndConditionsStep.js +233 -0
- package/lib/module/steps/TermsAndConditionsStep.js.map +1 -0
- package/lib/module/steps/_shared.js +33 -0
- package/lib/module/steps/_shared.js.map +1 -0
- package/lib/module/steps/types.js +4 -0
- package/lib/module/steps/types.js.map +1 -0
- package/lib/module/transport/api.js +173 -0
- package/lib/module/transport/api.js.map +1 -0
- package/lib/module/transport/irisWebSocket.js +115 -0
- package/lib/module/transport/irisWebSocket.js.map +1 -0
- package/lib/module/transport/secureUrl.js +45 -0
- package/lib/module/transport/secureUrl.js.map +1 -0
- package/lib/module/transport/sse.js +242 -0
- package/lib/module/transport/sse.js.map +1 -0
- package/lib/module/types/index.js +457 -0
- package/lib/module/types/index.js.map +1 -0
- package/lib/module/ui/MapFallbackBoundary.js +37 -0
- package/lib/module/ui/MapFallbackBoundary.js.map +1 -0
- package/lib/module/ui/StepShell.js +114 -0
- package/lib/module/ui/StepShell.js.map +1 -0
- package/lib/module/ui/VelionOnboardingScreen.js +212 -0
- package/lib/module/ui/VelionOnboardingScreen.js.map +1 -0
- package/lib/module/ui/forms/index.js +5 -0
- package/lib/module/ui/forms/index.js.map +1 -0
- package/lib/module/ui/forms/useInlineValidation.js +84 -0
- package/lib/module/ui/forms/useInlineValidation.js.map +1 -0
- package/lib/module/ui/forms/validators.js +27 -0
- package/lib/module/ui/forms/validators.js.map +1 -0
- package/lib/module/ui/icons/HomeHeartIcon.js +33 -0
- package/lib/module/ui/icons/HomeHeartIcon.js.map +1 -0
- package/lib/module/ui/icons/StepIcon.js +55 -0
- package/lib/module/ui/icons/StepIcon.js.map +1 -0
- package/lib/module/ui/overlays/AnalyzingOverlay.js +279 -0
- package/lib/module/ui/overlays/AnalyzingOverlay.js.map +1 -0
- package/lib/module/ui/overlays/LoadingOverlay.js +104 -0
- package/lib/module/ui/overlays/LoadingOverlay.js.map +1 -0
- package/lib/module/ui/overlays/ProgressBar.js +90 -0
- package/lib/module/ui/overlays/ProgressBar.js.map +1 -0
- package/lib/module/ui/overlays/RetryDialog.js +121 -0
- package/lib/module/ui/overlays/RetryDialog.js.map +1 -0
- package/lib/module/ui/overlays/index.js +6 -0
- package/lib/module/ui/overlays/index.js.map +1 -0
- package/lib/module/ui/primitives/Banner.js +77 -0
- package/lib/module/ui/primitives/Banner.js.map +1 -0
- package/lib/module/ui/primitives/Button.js +138 -0
- package/lib/module/ui/primitives/Button.js.map +1 -0
- package/lib/module/ui/primitives/Card.js +46 -0
- package/lib/module/ui/primitives/Card.js.map +1 -0
- package/lib/module/ui/primitives/Checkbox.js +136 -0
- package/lib/module/ui/primitives/Checkbox.js.map +1 -0
- package/lib/module/ui/primitives/CountrySelect.js +284 -0
- package/lib/module/ui/primitives/CountrySelect.js.map +1 -0
- package/lib/module/ui/primitives/DatePicker.js +241 -0
- package/lib/module/ui/primitives/DatePicker.js.map +1 -0
- package/lib/module/ui/primitives/Input.js +97 -0
- package/lib/module/ui/primitives/Input.js.map +1 -0
- package/lib/module/ui/primitives/Select.js +205 -0
- package/lib/module/ui/primitives/Select.js.map +1 -0
- package/lib/module/ui/primitives/Spinner.js +21 -0
- package/lib/module/ui/primitives/Spinner.js.map +1 -0
- package/lib/module/ui/primitives/index.js +12 -0
- package/lib/module/ui/primitives/index.js.map +1 -0
- package/lib/module/ui/theme/ThemeProvider.js +67 -0
- package/lib/module/ui/theme/ThemeProvider.js.map +1 -0
- package/lib/module/ui/theme/index.js +5 -0
- package/lib/module/ui/theme/index.js.map +1 -0
- package/lib/module/ui/theme/tokens.js +145 -0
- package/lib/module/ui/theme/tokens.js.map +1 -0
- package/lib/module/utils/EventEmitter.js +139 -0
- package/lib/module/utils/EventEmitter.js.map +1 -0
- package/lib/module/utils/base64.js +47 -0
- package/lib/module/utils/base64.js.map +1 -0
- package/lib/module/utils/deviceFingerprint.js +129 -0
- package/lib/module/utils/deviceFingerprint.js.map +1 -0
- package/lib/module/utils/documentTypeOptions.js +31 -0
- package/lib/module/utils/documentTypeOptions.js.map +1 -0
- package/lib/module/utils/googleMapsPreflight.js +73 -0
- package/lib/module/utils/googleMapsPreflight.js.map +1 -0
- package/lib/module/utils/hmac.js +53 -0
- package/lib/module/utils/hmac.js.map +1 -0
- package/lib/module/utils/iso3166Countries.js +92 -0
- package/lib/module/utils/iso3166Countries.js.map +1 -0
- package/lib/module/utils/jpegFromUri.js +40 -0
- package/lib/module/utils/jpegFromUri.js.map +1 -0
- package/lib/module/utils/keepAwake.js +53 -0
- package/lib/module/utils/keepAwake.js.map +1 -0
- package/lib/module/utils/log.js +37 -0
- package/lib/module/utils/log.js.map +1 -0
- package/lib/module/utils/osGeocoder.js +65 -0
- package/lib/module/utils/osGeocoder.js.map +1 -0
- package/lib/module/utils/parseGoogleAddressComponents.js +69 -0
- package/lib/module/utils/parseGoogleAddressComponents.js.map +1 -0
- package/lib/module/utils/personNameNormalize.js +20 -0
- package/lib/module/utils/personNameNormalize.js.map +1 -0
- package/lib/module/utils/sdkError.js +29 -0
- package/lib/module/utils/sdkError.js.map +1 -0
- package/lib/module/utils/sdkVersion.js +9 -0
- package/lib/module/utils/sdkVersion.js.map +1 -0
- package/lib/module/utils/stepConfig.js +59 -0
- package/lib/module/utils/stepConfig.js.map +1 -0
- package/lib/module/utils/taxInfoConfig.js +15 -0
- package/lib/module/utils/taxInfoConfig.js.map +1 -0
- package/lib/typescript/camera/AddressSearchBar.d.ts +37 -0
- package/lib/typescript/camera/AddressSearchBar.d.ts.map +1 -0
- package/lib/typescript/camera/DocumentCapture.d.ts +34 -0
- package/lib/typescript/camera/DocumentCapture.d.ts.map +1 -0
- package/lib/typescript/camera/LivenessCamera.d.ts +50 -0
- package/lib/typescript/camera/LivenessCamera.d.ts.map +1 -0
- package/lib/typescript/camera/MapAddressPicker.d.ts +57 -0
- package/lib/typescript/camera/MapAddressPicker.d.ts.map +1 -0
- package/lib/typescript/core/VelionOnboarding.d.ts +115 -0
- package/lib/typescript/core/VelionOnboarding.d.ts.map +1 -0
- package/lib/typescript/core/sessionDeclinedCopy.d.ts +16 -0
- package/lib/typescript/core/sessionDeclinedCopy.d.ts.map +1 -0
- package/lib/typescript/core/sessionStateMapper.d.ts +33 -0
- package/lib/typescript/core/sessionStateMapper.d.ts.map +1 -0
- package/lib/typescript/core/sseReasonMessages.d.ts +20 -0
- package/lib/typescript/core/sseReasonMessages.d.ts.map +1 -0
- package/lib/typescript/i18n/index.d.ts +13 -0
- package/lib/typescript/i18n/index.d.ts.map +1 -0
- package/lib/typescript/i18n/strings/en.d.ts +3 -0
- package/lib/typescript/i18n/strings/en.d.ts.map +1 -0
- package/lib/typescript/i18n/strings/es.d.ts +260 -0
- package/lib/typescript/i18n/strings/es.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +26 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/steps/AddressInfoStep.d.ts +26 -0
- package/lib/typescript/steps/AddressInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/DocumentsOcrStep.d.ts +26 -0
- package/lib/typescript/steps/DocumentsOcrStep.d.ts.map +1 -0
- package/lib/typescript/steps/EmailContactStep.d.ts +15 -0
- package/lib/typescript/steps/EmailContactStep.d.ts.map +1 -0
- package/lib/typescript/steps/EmailOtpStep.d.ts +21 -0
- package/lib/typescript/steps/EmailOtpStep.d.ts.map +1 -0
- package/lib/typescript/steps/FaceMatchStep.d.ts +27 -0
- package/lib/typescript/steps/FaceMatchStep.d.ts.map +1 -0
- package/lib/typescript/steps/FingerprintStep.d.ts +25 -0
- package/lib/typescript/steps/FingerprintStep.d.ts.map +1 -0
- package/lib/typescript/steps/HumanBasicInfoStep.d.ts +25 -0
- package/lib/typescript/steps/HumanBasicInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/HumanExtendedInfoStep.d.ts +23 -0
- package/lib/typescript/steps/HumanExtendedInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/LegalBasicInfoStep.d.ts +17 -0
- package/lib/typescript/steps/LegalBasicInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/LegalExtendedInfoStep.d.ts +17 -0
- package/lib/typescript/steps/LegalExtendedInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/LivenessMatchStep.d.ts +26 -0
- package/lib/typescript/steps/LivenessMatchStep.d.ts.map +1 -0
- package/lib/typescript/steps/PhoneContactStep.d.ts +15 -0
- package/lib/typescript/steps/PhoneContactStep.d.ts.map +1 -0
- package/lib/typescript/steps/PhoneOtpStep.d.ts +19 -0
- package/lib/typescript/steps/PhoneOtpStep.d.ts.map +1 -0
- package/lib/typescript/steps/SwornDeclarationStep.d.ts +20 -0
- package/lib/typescript/steps/SwornDeclarationStep.d.ts.map +1 -0
- package/lib/typescript/steps/TaxInfoStep.d.ts +19 -0
- package/lib/typescript/steps/TaxInfoStep.d.ts.map +1 -0
- package/lib/typescript/steps/TermsAndConditionsStep.d.ts +21 -0
- package/lib/typescript/steps/TermsAndConditionsStep.d.ts.map +1 -0
- package/lib/typescript/steps/_shared.d.ts +11 -0
- package/lib/typescript/steps/_shared.d.ts.map +1 -0
- package/lib/typescript/steps/types.d.ts +68 -0
- package/lib/typescript/steps/types.d.ts.map +1 -0
- package/lib/typescript/transport/api.d.ts +35 -0
- package/lib/typescript/transport/api.d.ts.map +1 -0
- package/lib/typescript/transport/irisWebSocket.d.ts +72 -0
- package/lib/typescript/transport/irisWebSocket.d.ts.map +1 -0
- package/lib/typescript/transport/secureUrl.d.ts +14 -0
- package/lib/typescript/transport/secureUrl.d.ts.map +1 -0
- package/lib/typescript/transport/sse.d.ts +54 -0
- package/lib/typescript/transport/sse.d.ts.map +1 -0
- package/lib/typescript/types/index.d.ts +1010 -0
- package/lib/typescript/types/index.d.ts.map +1 -0
- package/lib/typescript/ui/MapFallbackBoundary.d.ts +28 -0
- package/lib/typescript/ui/MapFallbackBoundary.d.ts.map +1 -0
- package/lib/typescript/ui/StepShell.d.ts +19 -0
- package/lib/typescript/ui/StepShell.d.ts.map +1 -0
- package/lib/typescript/ui/VelionOnboardingScreen.d.ts +19 -0
- package/lib/typescript/ui/VelionOnboardingScreen.d.ts.map +1 -0
- package/lib/typescript/ui/forms/index.d.ts +3 -0
- package/lib/typescript/ui/forms/index.d.ts.map +1 -0
- package/lib/typescript/ui/forms/useInlineValidation.d.ts +30 -0
- package/lib/typescript/ui/forms/useInlineValidation.d.ts.map +1 -0
- package/lib/typescript/ui/forms/validators.d.ts +11 -0
- package/lib/typescript/ui/forms/validators.d.ts.map +1 -0
- package/lib/typescript/ui/icons/HomeHeartIcon.d.ts +14 -0
- package/lib/typescript/ui/icons/HomeHeartIcon.d.ts.map +1 -0
- package/lib/typescript/ui/icons/StepIcon.d.ts +26 -0
- package/lib/typescript/ui/icons/StepIcon.d.ts.map +1 -0
- package/lib/typescript/ui/overlays/AnalyzingOverlay.d.ts +24 -0
- package/lib/typescript/ui/overlays/AnalyzingOverlay.d.ts.map +1 -0
- package/lib/typescript/ui/overlays/LoadingOverlay.d.ts +19 -0
- package/lib/typescript/ui/overlays/LoadingOverlay.d.ts.map +1 -0
- package/lib/typescript/ui/overlays/ProgressBar.d.ts +26 -0
- package/lib/typescript/ui/overlays/ProgressBar.d.ts.map +1 -0
- package/lib/typescript/ui/overlays/RetryDialog.d.ts +17 -0
- package/lib/typescript/ui/overlays/RetryDialog.d.ts.map +1 -0
- package/lib/typescript/ui/overlays/index.d.ts +4 -0
- package/lib/typescript/ui/overlays/index.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Banner.d.ts +11 -0
- package/lib/typescript/ui/primitives/Banner.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Button.d.ts +27 -0
- package/lib/typescript/ui/primitives/Button.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Card.d.ts +10 -0
- package/lib/typescript/ui/primitives/Card.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Checkbox.d.ts +25 -0
- package/lib/typescript/ui/primitives/Checkbox.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/CountrySelect.d.ts +31 -0
- package/lib/typescript/ui/primitives/CountrySelect.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/DatePicker.d.ts +42 -0
- package/lib/typescript/ui/primitives/DatePicker.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Input.d.ts +17 -0
- package/lib/typescript/ui/primitives/Input.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Select.d.ts +26 -0
- package/lib/typescript/ui/primitives/Select.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/Spinner.d.ts +10 -0
- package/lib/typescript/ui/primitives/Spinner.d.ts.map +1 -0
- package/lib/typescript/ui/primitives/index.d.ts +10 -0
- package/lib/typescript/ui/primitives/index.d.ts.map +1 -0
- package/lib/typescript/ui/theme/ThemeProvider.d.ts +23 -0
- package/lib/typescript/ui/theme/ThemeProvider.d.ts.map +1 -0
- package/lib/typescript/ui/theme/index.d.ts +3 -0
- package/lib/typescript/ui/theme/index.d.ts.map +1 -0
- package/lib/typescript/ui/theme/tokens.d.ts +42 -0
- package/lib/typescript/ui/theme/tokens.d.ts.map +1 -0
- package/lib/typescript/utils/EventEmitter.d.ts +98 -0
- package/lib/typescript/utils/EventEmitter.d.ts.map +1 -0
- package/lib/typescript/utils/base64.d.ts +19 -0
- package/lib/typescript/utils/base64.d.ts.map +1 -0
- package/lib/typescript/utils/deviceFingerprint.d.ts +42 -0
- package/lib/typescript/utils/deviceFingerprint.d.ts.map +1 -0
- package/lib/typescript/utils/documentTypeOptions.d.ts +13 -0
- package/lib/typescript/utils/documentTypeOptions.d.ts.map +1 -0
- package/lib/typescript/utils/googleMapsPreflight.d.ts +44 -0
- package/lib/typescript/utils/googleMapsPreflight.d.ts.map +1 -0
- package/lib/typescript/utils/hmac.d.ts +39 -0
- package/lib/typescript/utils/hmac.d.ts.map +1 -0
- package/lib/typescript/utils/iso3166Countries.d.ts +27 -0
- package/lib/typescript/utils/iso3166Countries.d.ts.map +1 -0
- package/lib/typescript/utils/jpegFromUri.d.ts +29 -0
- package/lib/typescript/utils/jpegFromUri.d.ts.map +1 -0
- package/lib/typescript/utils/keepAwake.d.ts +16 -0
- package/lib/typescript/utils/keepAwake.d.ts.map +1 -0
- package/lib/typescript/utils/log.d.ts +18 -0
- package/lib/typescript/utils/log.d.ts.map +1 -0
- package/lib/typescript/utils/osGeocoder.d.ts +38 -0
- package/lib/typescript/utils/osGeocoder.d.ts.map +1 -0
- package/lib/typescript/utils/parseGoogleAddressComponents.d.ts +43 -0
- package/lib/typescript/utils/parseGoogleAddressComponents.d.ts.map +1 -0
- package/lib/typescript/utils/personNameNormalize.d.ts +10 -0
- package/lib/typescript/utils/personNameNormalize.d.ts.map +1 -0
- package/lib/typescript/utils/sdkError.d.ts +14 -0
- package/lib/typescript/utils/sdkError.d.ts.map +1 -0
- package/lib/typescript/utils/sdkVersion.d.ts +7 -0
- package/lib/typescript/utils/sdkVersion.d.ts.map +1 -0
- package/lib/typescript/utils/stepConfig.d.ts +8 -0
- package/lib/typescript/utils/stepConfig.d.ts.map +1 -0
- package/lib/typescript/utils/taxInfoConfig.d.ts +7 -0
- package/lib/typescript/utils/taxInfoConfig.d.ts.map +1 -0
- package/package.json +99 -0
- package/src/camera/AddressSearchBar.tsx +363 -0
- package/src/camera/DocumentCapture.tsx +505 -0
- package/src/camera/LivenessCamera.tsx +602 -0
- package/src/camera/MapAddressPicker.tsx +397 -0
- package/src/core/VelionOnboarding.ts +974 -0
- package/src/core/sessionDeclinedCopy.ts +76 -0
- package/src/core/sessionStateMapper.ts +167 -0
- package/src/core/sseReasonMessages.ts +531 -0
- package/src/data/iso3166Alpha2Codes.json +1 -0
- package/src/data/iso3166CountryNames.json +251 -0
- package/src/i18n/index.ts +26 -0
- package/src/i18n/strings/en.ts +289 -0
- package/src/i18n/strings/es.ts +291 -0
- package/src/index.ts +137 -0
- package/src/steps/AddressInfoStep.tsx +726 -0
- package/src/steps/DocumentsOcrStep.tsx +472 -0
- package/src/steps/EmailContactStep.tsx +159 -0
- package/src/steps/EmailOtpStep.tsx +250 -0
- package/src/steps/FaceMatchStep.tsx +177 -0
- package/src/steps/FingerprintStep.tsx +99 -0
- package/src/steps/HumanBasicInfoStep.tsx +274 -0
- package/src/steps/HumanExtendedInfoStep.tsx +353 -0
- package/src/steps/LegalBasicInfoStep.tsx +205 -0
- package/src/steps/LegalExtendedInfoStep.tsx +171 -0
- package/src/steps/LivenessMatchStep.tsx +846 -0
- package/src/steps/PhoneContactStep.tsx +160 -0
- package/src/steps/PhoneOtpStep.tsx +249 -0
- package/src/steps/SwornDeclarationStep.tsx +220 -0
- package/src/steps/TaxInfoStep.tsx +277 -0
- package/src/steps/TermsAndConditionsStep.tsx +261 -0
- package/src/steps/_shared.ts +44 -0
- package/src/steps/types.ts +72 -0
- package/src/transport/api.ts +257 -0
- package/src/transport/irisWebSocket.ts +186 -0
- package/src/transport/secureUrl.ts +47 -0
- package/src/transport/sse.ts +310 -0
- package/src/types/index.ts +1198 -0
- package/src/ui/MapFallbackBoundary.tsx +46 -0
- package/src/ui/StepShell.tsx +137 -0
- package/src/ui/VelionOnboardingScreen.tsx +223 -0
- package/src/ui/forms/index.ts +12 -0
- package/src/ui/forms/useInlineValidation.ts +115 -0
- package/src/ui/forms/validators.ts +29 -0
- package/src/ui/icons/HomeHeartIcon.tsx +34 -0
- package/src/ui/icons/StepIcon.tsx +77 -0
- package/src/ui/overlays/AnalyzingOverlay.tsx +323 -0
- package/src/ui/overlays/LoadingOverlay.tsx +115 -0
- package/src/ui/overlays/ProgressBar.tsx +99 -0
- package/src/ui/overlays/RetryDialog.tsx +128 -0
- package/src/ui/overlays/index.ts +3 -0
- package/src/ui/primitives/Banner.tsx +81 -0
- package/src/ui/primitives/Button.tsx +164 -0
- package/src/ui/primitives/Card.tsx +55 -0
- package/src/ui/primitives/Checkbox.tsx +153 -0
- package/src/ui/primitives/CountrySelect.tsx +356 -0
- package/src/ui/primitives/DatePicker.tsx +266 -0
- package/src/ui/primitives/Input.tsx +110 -0
- package/src/ui/primitives/Select.tsx +259 -0
- package/src/ui/primitives/Spinner.tsx +17 -0
- package/src/ui/primitives/index.ts +9 -0
- package/src/ui/theme/ThemeProvider.tsx +102 -0
- package/src/ui/theme/index.ts +17 -0
- package/src/ui/theme/tokens.ts +159 -0
- package/src/utils/EventEmitter.ts +155 -0
- package/src/utils/base64.ts +44 -0
- package/src/utils/deviceFingerprint.ts +184 -0
- package/src/utils/documentTypeOptions.ts +31 -0
- package/src/utils/googleMapsPreflight.ts +72 -0
- package/src/utils/hmac.ts +61 -0
- package/src/utils/iso3166Countries.ts +111 -0
- package/src/utils/jpegFromUri.ts +42 -0
- package/src/utils/keepAwake.ts +62 -0
- package/src/utils/log.ts +37 -0
- package/src/utils/osGeocoder.ts +75 -0
- package/src/utils/parseGoogleAddressComponents.ts +123 -0
- package/src/utils/personNameNormalize.ts +17 -0
- package/src/utils/sdkError.ts +40 -0
- package/src/utils/sdkVersion.ts +6 -0
- package/src/utils/stepConfig.ts +75 -0
- package/src/utils/taxInfoConfig.ts +14 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LIVENESS_MATCH — Iris-driven active liveness over WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Flow:
|
|
5
|
+
* 1. POST /liveness-init → { liveness_session_id, websocket_url, expires_at,
|
|
6
|
+
* challenges, connection_token, frame_integrity_key_base64 }.
|
|
7
|
+
* 2. Optionally import the HMAC key for frame integrity.
|
|
8
|
+
* 3. Open vision-camera (front), connect WS with ?token & ?lang.
|
|
9
|
+
* 4. SESSION_READY → start frame capture loop (interval-driven), watchdog 25s.
|
|
10
|
+
* 5. Process CHALLENGE / CHALLENGE_PROGRESS / CHALLENGE_COMPLETE / WARNING /
|
|
11
|
+
* ERROR / SESSION_COMPLETE.
|
|
12
|
+
* 6. Terminal WARNING (SPOOF_RISK_HIGH, …) → in-flow retry dialog (re-init
|
|
13
|
+
* + fresh WS) without bailing the whole flow.
|
|
14
|
+
* 7. SESSION_COMPLETE → POST /liveness-verify → resolve the step.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
18
|
+
import { ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
19
|
+
import * as ImageManipulator from 'expo-image-manipulator';
|
|
20
|
+
import {
|
|
21
|
+
SdkErrorCode,
|
|
22
|
+
type LivenessSessionResponse,
|
|
23
|
+
type SdkError,
|
|
24
|
+
} from '../types';
|
|
25
|
+
import { Banner } from '../ui/primitives/Banner';
|
|
26
|
+
import { Button } from '../ui/primitives/Button';
|
|
27
|
+
import { AnalyzingOverlay } from '../ui/overlays/AnalyzingOverlay';
|
|
28
|
+
import { RetryDialog } from '../ui/overlays/RetryDialog';
|
|
29
|
+
import { StepShell } from '../ui/StepShell';
|
|
30
|
+
import { StepIcon } from '../ui/icons/StepIcon';
|
|
31
|
+
import { mixHex, useTheme } from '../ui/theme';
|
|
32
|
+
import { getStrings } from '../i18n';
|
|
33
|
+
import { log } from '../utils/log';
|
|
34
|
+
import { readJpegFromUri } from '../utils/jpegFromUri';
|
|
35
|
+
import {
|
|
36
|
+
buildSignedFrameEnvelope,
|
|
37
|
+
importHmacKey,
|
|
38
|
+
type HmacKey,
|
|
39
|
+
} from '../utils/hmac';
|
|
40
|
+
import {
|
|
41
|
+
TERMINAL_WARNING_CODES,
|
|
42
|
+
openIrisWebSocket,
|
|
43
|
+
type IrisChallenge,
|
|
44
|
+
type IrisConnection,
|
|
45
|
+
type IrisServerMessage,
|
|
46
|
+
} from '../transport/irisWebSocket';
|
|
47
|
+
import {
|
|
48
|
+
LivenessCamera,
|
|
49
|
+
type LivenessCameraHandle,
|
|
50
|
+
type LivenessOvalCue,
|
|
51
|
+
} from '../camera/LivenessCamera';
|
|
52
|
+
import type { ApiCaller } from '../transport/api';
|
|
53
|
+
import type { StepDeps, StepModule } from './types';
|
|
54
|
+
|
|
55
|
+
// Capture loop defaults tuned for the Iris orchestrator's frame cadence.
|
|
56
|
+
const FRAME_MS_DEFAULT = 350;
|
|
57
|
+
const FRAME_MS_TURN = 220;
|
|
58
|
+
const PROGRESS_WATCHDOG_MS = 25_000;
|
|
59
|
+
const FRAME_MAX_LONG_EDGE = 720;
|
|
60
|
+
const FRAME_QUALITY = 0.55;
|
|
61
|
+
|
|
62
|
+
export class LivenessMatchStep implements StepModule {
|
|
63
|
+
private resolveStep!: () => void;
|
|
64
|
+
private rejectStep!: (err: SdkError) => void;
|
|
65
|
+
private destroyed = false;
|
|
66
|
+
|
|
67
|
+
constructor(private readonly deps: StepDeps) {}
|
|
68
|
+
|
|
69
|
+
run(): Promise<void> {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
this.resolveStep = resolve;
|
|
72
|
+
this.rejectStep = reject;
|
|
73
|
+
this.deps.ui.setNode(
|
|
74
|
+
<LivenessMatchView
|
|
75
|
+
api={this.deps.api}
|
|
76
|
+
sessionId={this.deps.sessionId}
|
|
77
|
+
locale={this.deps.locale}
|
|
78
|
+
calibrationLabel={this.deps.livenessCalibrationLabel}
|
|
79
|
+
showIntro
|
|
80
|
+
showBackButton
|
|
81
|
+
onComplete={() => {
|
|
82
|
+
if (this.destroyed) return;
|
|
83
|
+
this.deps.ui.setNode(null);
|
|
84
|
+
this.resolveStep();
|
|
85
|
+
}}
|
|
86
|
+
onCancel={(err) => {
|
|
87
|
+
if (this.destroyed) return;
|
|
88
|
+
this.deps.ui.setNode(null);
|
|
89
|
+
this.rejectStep(err);
|
|
90
|
+
}}
|
|
91
|
+
/>,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
destroy(): void {
|
|
97
|
+
this.destroyed = true;
|
|
98
|
+
this.deps.ui.setNode(null);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
interface ViewProps {
|
|
103
|
+
api: ApiCaller;
|
|
104
|
+
sessionId: string;
|
|
105
|
+
locale: string;
|
|
106
|
+
/** Optional Iris calibration label forwarded to /liveness-init body. */
|
|
107
|
+
calibrationLabel?: string | undefined;
|
|
108
|
+
showIntro: boolean;
|
|
109
|
+
showBackButton: boolean;
|
|
110
|
+
onComplete: () => void;
|
|
111
|
+
onCancel: (err: SdkError) => void;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type Phase = 'intro' | 'capturing' | 'finalizing';
|
|
115
|
+
|
|
116
|
+
function LivenessMatchView({
|
|
117
|
+
api,
|
|
118
|
+
sessionId,
|
|
119
|
+
locale,
|
|
120
|
+
calibrationLabel,
|
|
121
|
+
showIntro,
|
|
122
|
+
showBackButton,
|
|
123
|
+
onComplete,
|
|
124
|
+
onCancel,
|
|
125
|
+
}: ViewProps) {
|
|
126
|
+
const t = getStrings(locale);
|
|
127
|
+
const theme = useTheme();
|
|
128
|
+
const cameraRef = useRef<LivenessCameraHandle>(null);
|
|
129
|
+
|
|
130
|
+
const [phase, setPhase] = useState<Phase>(showIntro ? 'intro' : 'capturing');
|
|
131
|
+
const [statusText, setStatusText] = useState<string | null>(null);
|
|
132
|
+
const [challengeText, setChallengeText] = useState<string | null>(null);
|
|
133
|
+
const [ovalCue, setOvalCue] = useState<LivenessOvalCue>('NEUTRAL');
|
|
134
|
+
const [toast, setToast] = useState<
|
|
135
|
+
| {
|
|
136
|
+
kind: 'success' | 'warning';
|
|
137
|
+
message: string;
|
|
138
|
+
}
|
|
139
|
+
| null
|
|
140
|
+
>(null);
|
|
141
|
+
const [progress, setProgress] = useState<number | null>(null);
|
|
142
|
+
const [retry, setRetry] = useState<{ message: string; code: string } | null>(null);
|
|
143
|
+
const [bannerError, setBannerError] = useState<string | null>(null);
|
|
144
|
+
|
|
145
|
+
// Long-lived state that the capture loop / WS handlers need without
|
|
146
|
+
// triggering re-renders: kept in refs.
|
|
147
|
+
const wsRef = useRef<IrisConnection | null>(null);
|
|
148
|
+
const hmacRef = useRef<HmacKey | null>(null);
|
|
149
|
+
const livenessSessionIdRef = useRef<string | null>(null);
|
|
150
|
+
const currentChallengeRef = useRef<IrisChallenge | null>(null);
|
|
151
|
+
// Tracks the position of the active challenge inside the session sequence
|
|
152
|
+
// so we can recognise the LAST challenge completing and switch to the
|
|
153
|
+
// "Analyzing" overlay immediately, instead of letting the user stare at
|
|
154
|
+
// the camera with the last instruction frozen on screen for the seconds
|
|
155
|
+
// it takes the worker to drain its queue and the orchestrator to emit
|
|
156
|
+
// SESSION_COMPLETE.
|
|
157
|
+
const lastChallengeMetaRef = useRef<{ index: number; total: number } | null>(
|
|
158
|
+
null,
|
|
159
|
+
);
|
|
160
|
+
const frameMsRef = useRef<number>(FRAME_MS_DEFAULT);
|
|
161
|
+
const captureTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
162
|
+
const inFlightRef = useRef(false);
|
|
163
|
+
const framesSentRef = useRef(0);
|
|
164
|
+
const watchdogRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
165
|
+
const finalizingRef = useRef(false);
|
|
166
|
+
const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
167
|
+
// Tracks the toast currently on screen so we can extend its visibility when
|
|
168
|
+
// the same message arrives again (instead of dismount/remount, which made
|
|
169
|
+
// warnings flash in <2s and the user couldn't read them).
|
|
170
|
+
const activeToastRef = useRef<{ kind: 'success' | 'warning'; message: string } | null>(null);
|
|
171
|
+
|
|
172
|
+
// Each toast stays on screen at least this long. The orchestrator can spam
|
|
173
|
+
// WARNINGs (POSITIONING_REQUIRED, SPOOF_RISK_HIGH, …) several times per
|
|
174
|
+
// second; without a floor the user literally cannot read them.
|
|
175
|
+
const TOAST_MIN_DURATION_MS = 3200;
|
|
176
|
+
|
|
177
|
+
const showToast = useCallback(
|
|
178
|
+
(kind: 'success' | 'warning', message: string) => {
|
|
179
|
+
const active = activeToastRef.current;
|
|
180
|
+
if (active && active.kind === kind && active.message === message) {
|
|
181
|
+
// Identical toast already visible — just refresh the timer so it
|
|
182
|
+
// stays up while warnings keep arriving. Do NOT touch `setToast`:
|
|
183
|
+
// a new object would re-trigger the entry animation and flicker.
|
|
184
|
+
if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
|
|
185
|
+
toastTimerRef.current = setTimeout(() => {
|
|
186
|
+
setToast(null);
|
|
187
|
+
activeToastRef.current = null;
|
|
188
|
+
}, TOAST_MIN_DURATION_MS);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
// Different content: replace and guarantee the new toast its own
|
|
192
|
+
// minimum window.
|
|
193
|
+
activeToastRef.current = { kind, message };
|
|
194
|
+
setToast({ kind, message });
|
|
195
|
+
if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
|
|
196
|
+
toastTimerRef.current = setTimeout(() => {
|
|
197
|
+
setToast(null);
|
|
198
|
+
activeToastRef.current = null;
|
|
199
|
+
}, TOAST_MIN_DURATION_MS);
|
|
200
|
+
},
|
|
201
|
+
[],
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const stopWatchdog = useCallback(() => {
|
|
205
|
+
if (watchdogRef.current) {
|
|
206
|
+
clearTimeout(watchdogRef.current);
|
|
207
|
+
watchdogRef.current = null;
|
|
208
|
+
}
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const stopCapture = useCallback(() => {
|
|
212
|
+
if (captureTimerRef.current) {
|
|
213
|
+
clearInterval(captureTimerRef.current);
|
|
214
|
+
captureTimerRef.current = null;
|
|
215
|
+
}
|
|
216
|
+
inFlightRef.current = false;
|
|
217
|
+
}, []);
|
|
218
|
+
|
|
219
|
+
const failWith = useCallback(
|
|
220
|
+
(err: SdkError) => {
|
|
221
|
+
stopWatchdog();
|
|
222
|
+
stopCapture();
|
|
223
|
+
try {
|
|
224
|
+
wsRef.current?.close(1000, 'fail');
|
|
225
|
+
} catch {
|
|
226
|
+
/* ignore */
|
|
227
|
+
}
|
|
228
|
+
wsRef.current = null;
|
|
229
|
+
onCancel(err);
|
|
230
|
+
},
|
|
231
|
+
[onCancel, stopCapture, stopWatchdog],
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const captureAndSend = useCallback(async () => {
|
|
235
|
+
if (inFlightRef.current) return;
|
|
236
|
+
const ws = wsRef.current;
|
|
237
|
+
if (!ws || !ws.isOpen()) return;
|
|
238
|
+
inFlightRef.current = true;
|
|
239
|
+
try {
|
|
240
|
+
const photo = await cameraRef.current?.takeFrame();
|
|
241
|
+
if (!photo || !ws.isOpen()) return;
|
|
242
|
+
const compressed = await ImageManipulator.manipulateAsync(
|
|
243
|
+
photo.path.startsWith('file://') ? photo.path : `file://${photo.path}`,
|
|
244
|
+
[{ resize: { width: FRAME_MAX_LONG_EDGE } }],
|
|
245
|
+
{ compress: FRAME_QUALITY, format: ImageManipulator.SaveFormat.JPEG },
|
|
246
|
+
);
|
|
247
|
+
if (!ws.isOpen()) return;
|
|
248
|
+
const jpeg = await readJpegFromUri(compressed.uri);
|
|
249
|
+
const key = hmacRef.current;
|
|
250
|
+
if (key) {
|
|
251
|
+
const envelope = buildSignedFrameEnvelope({
|
|
252
|
+
key,
|
|
253
|
+
sessionId: livenessSessionIdRef.current ?? sessionId,
|
|
254
|
+
frameId: framesSentRef.current,
|
|
255
|
+
jpegBytes: jpeg.bytes,
|
|
256
|
+
jpegBase64: jpeg.base64,
|
|
257
|
+
});
|
|
258
|
+
ws.sendJson(envelope.json);
|
|
259
|
+
} else {
|
|
260
|
+
// Vision-camera + RN's WebSocket need an ArrayBuffer for binary frames.
|
|
261
|
+
// `Uint8Array.buffer` is a `SharedArrayBuffer` on some Hermes versions
|
|
262
|
+
// — slice into a fresh ArrayBuffer to be safe.
|
|
263
|
+
const ab = jpeg.bytes.buffer.slice(
|
|
264
|
+
jpeg.bytes.byteOffset,
|
|
265
|
+
jpeg.bytes.byteOffset + jpeg.bytes.byteLength,
|
|
266
|
+
) as ArrayBuffer;
|
|
267
|
+
ws.sendBinary(ab);
|
|
268
|
+
}
|
|
269
|
+
framesSentRef.current += 1;
|
|
270
|
+
} catch (err) {
|
|
271
|
+
log.warn('[Liveness] frame send failed', err);
|
|
272
|
+
} finally {
|
|
273
|
+
inFlightRef.current = false;
|
|
274
|
+
}
|
|
275
|
+
}, [sessionId]);
|
|
276
|
+
|
|
277
|
+
const startCaptureLoop = useCallback(
|
|
278
|
+
(ms: number) => {
|
|
279
|
+
stopCapture();
|
|
280
|
+
frameMsRef.current = ms;
|
|
281
|
+
captureTimerRef.current = setInterval(() => {
|
|
282
|
+
void captureAndSend();
|
|
283
|
+
}, ms);
|
|
284
|
+
},
|
|
285
|
+
[captureAndSend, stopCapture],
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const adjustFps = useCallback(
|
|
289
|
+
(ms: number) => {
|
|
290
|
+
if (frameMsRef.current === ms) return;
|
|
291
|
+
if (captureTimerRef.current) {
|
|
292
|
+
startCaptureLoop(ms);
|
|
293
|
+
} else {
|
|
294
|
+
frameMsRef.current = ms;
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
[startCaptureLoop],
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const challengeLabel = useCallback(
|
|
301
|
+
(id: IrisChallenge | null): string => {
|
|
302
|
+
if (!id) return '';
|
|
303
|
+
return t.liveness.challenge[id] ?? '';
|
|
304
|
+
},
|
|
305
|
+
[t.liveness.challenge],
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const presentTerminalRetry = useCallback(
|
|
309
|
+
(code: string, message?: string) => {
|
|
310
|
+
stopCapture();
|
|
311
|
+
stopWatchdog();
|
|
312
|
+
try {
|
|
313
|
+
wsRef.current?.close(4001, 'terminal_warning_retry_pending');
|
|
314
|
+
} catch {
|
|
315
|
+
/* ignore */
|
|
316
|
+
}
|
|
317
|
+
wsRef.current = null;
|
|
318
|
+
setRetry({ code, message: message?.trim() || t.liveness.terminalRetryFallback });
|
|
319
|
+
},
|
|
320
|
+
[stopCapture, stopWatchdog, t.liveness.terminalRetryFallback],
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const resetWatchdog = useCallback(() => {
|
|
324
|
+
stopWatchdog();
|
|
325
|
+
watchdogRef.current = setTimeout(() => {
|
|
326
|
+
log.warn('[Liveness] progress watchdog fired');
|
|
327
|
+
// The watchdog used to be a hard fail — onCancel → step rejects →
|
|
328
|
+
// orchestrator surfaces the error and exits the whole liveness step.
|
|
329
|
+
// That left the user with no in-flow recovery: they had to restart
|
|
330
|
+
// the entire onboarding. The Iris-side "no progress in N seconds" is
|
|
331
|
+
// usually a recoverable user-side issue (lighting, framing, not
|
|
332
|
+
// looking at the camera), so we treat it the same way as a terminal
|
|
333
|
+
// WARNING: tear down the WS, show the retry dialog, and let the user
|
|
334
|
+
// restart the verification without leaving the step.
|
|
335
|
+
presentTerminalRetry('NO_PROGRESS', t.liveness.noProgressMessage);
|
|
336
|
+
}, PROGRESS_WATCHDOG_MS);
|
|
337
|
+
}, [presentTerminalRetry, stopWatchdog, t.liveness.noProgressMessage]);
|
|
338
|
+
|
|
339
|
+
const handleWsMessage = useCallback(
|
|
340
|
+
(msg: IrisServerMessage) => {
|
|
341
|
+
switch (msg.type) {
|
|
342
|
+
case 'SESSION_READY': {
|
|
343
|
+
setStatusText(null);
|
|
344
|
+
// The ~1-2 s gap between SESSION_READY and the first CHALLENGE is
|
|
345
|
+
// where users decide their posture for the whole session. We use
|
|
346
|
+
// it for two pieces of guidance, layered:
|
|
347
|
+
// - A short "Heads up — starting now" toast above the oval so
|
|
348
|
+
// they're not caught looking away when (e.g.) the BLINK window
|
|
349
|
+
// opens.
|
|
350
|
+
// - A persistent `challengeText` ("Center your face in the oval")
|
|
351
|
+
// anchored above the oval so they keep adjusting until the
|
|
352
|
+
// first real challenge arrives and overwrites it. Without
|
|
353
|
+
// this, every Android calibration session started with the
|
|
354
|
+
// face at ~25 % of the frame width and SMILE / MOVE_FARTHER
|
|
355
|
+
// failed before the user understood what was being asked.
|
|
356
|
+
setChallengeText(t.liveness.centerFace);
|
|
357
|
+
showToast('success', t.liveness.getReady);
|
|
358
|
+
startCaptureLoop(FRAME_MS_DEFAULT);
|
|
359
|
+
resetWatchdog();
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
case 'CHALLENGE': {
|
|
363
|
+
const ch = (msg.challenge ?? null) as IrisChallenge | null;
|
|
364
|
+
currentChallengeRef.current = ch;
|
|
365
|
+
// Stash the sequence position so CHALLENGE_COMPLETE can tell
|
|
366
|
+
// whether this was the LAST challenge and switch to the analyzing
|
|
367
|
+
// overlay immediately instead of waiting for SESSION_COMPLETE.
|
|
368
|
+
if (
|
|
369
|
+
typeof msg.sequenceIndex === 'number' &&
|
|
370
|
+
typeof msg.totalChallenges === 'number'
|
|
371
|
+
) {
|
|
372
|
+
lastChallengeMetaRef.current = {
|
|
373
|
+
index: msg.sequenceIndex,
|
|
374
|
+
total: msg.totalChallenges,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
adjustFps(
|
|
378
|
+
ch === 'TURN_LEFT' || ch === 'TURN_RIGHT'
|
|
379
|
+
? FRAME_MS_TURN
|
|
380
|
+
: FRAME_MS_DEFAULT,
|
|
381
|
+
);
|
|
382
|
+
setChallengeText(challengeLabel(ch));
|
|
383
|
+
// Map distance challenges to an oval-size cue so the viewfinder
|
|
384
|
+
// shape reinforces the instruction (grows for closer, returns to
|
|
385
|
+
// neutral for farther). All other challenges keep the neutral
|
|
386
|
+
// oval — the dim mask shouldn't move under a turn / smile / blink.
|
|
387
|
+
setOvalCue(
|
|
388
|
+
ch === 'MOVE_CLOSER'
|
|
389
|
+
? 'MOVE_CLOSER'
|
|
390
|
+
: ch === 'MOVE_FARTHER'
|
|
391
|
+
? 'MOVE_FARTHER'
|
|
392
|
+
: 'NEUTRAL',
|
|
393
|
+
);
|
|
394
|
+
setProgress(0);
|
|
395
|
+
resetWatchdog();
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
case 'CHALLENGE_PROGRESS': {
|
|
399
|
+
if (typeof msg.confidence === 'number') {
|
|
400
|
+
setProgress(msg.confidence);
|
|
401
|
+
}
|
|
402
|
+
resetWatchdog();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
case 'CHALLENGE_COMPLETE': {
|
|
406
|
+
setProgress(1);
|
|
407
|
+
currentChallengeRef.current = null;
|
|
408
|
+
adjustFps(FRAME_MS_DEFAULT);
|
|
409
|
+
// Reset the oval back to neutral after each challenge so the
|
|
410
|
+
// next CHALLENGE (or finalize) doesn't inherit a "zoomed" oval.
|
|
411
|
+
setOvalCue('NEUTRAL');
|
|
412
|
+
// Last challenge of the sequence completed → switch to the
|
|
413
|
+
// "Analyzing your video" overlay right away. SESSION_COMPLETE
|
|
414
|
+
// can take several seconds to land while the worker drains
|
|
415
|
+
// its queue; without this the user would stare at the camera
|
|
416
|
+
// and the stale instruction the whole time.
|
|
417
|
+
const meta = lastChallengeMetaRef.current;
|
|
418
|
+
if (meta && meta.index >= meta.total - 1) {
|
|
419
|
+
stopCapture();
|
|
420
|
+
setPhase('finalizing');
|
|
421
|
+
}
|
|
422
|
+
resetWatchdog();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
case 'WARNING': {
|
|
426
|
+
if (msg.code && TERMINAL_WARNING_CODES.has(msg.code)) {
|
|
427
|
+
presentTerminalRetry(msg.code, msg.message);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
const isPositioningReady = msg.code === 'POSITIONING_READY';
|
|
431
|
+
// Per-code SDK-side fallback used when the orchestrator forwarded a
|
|
432
|
+
// code but no localised text (e.g. older orchestrator versions, or
|
|
433
|
+
// the rare WS message that drops the body). The orchestrator's own
|
|
434
|
+
// text is preferred — it always wins when present — so the user sees
|
|
435
|
+
// the server's exact wording in the session's locale.
|
|
436
|
+
const FALLBACK_BY_CODE: Record<string, string> = {
|
|
437
|
+
TOO_FAR: t.liveness.warningTooFar,
|
|
438
|
+
TOO_CLOSE: t.liveness.warningTooClose,
|
|
439
|
+
MOVE_CLOSER_HINT: t.liveness.warningMoveCloserHint,
|
|
440
|
+
MOVE_FARTHER_HINT: t.liveness.warningMoveFartherHint,
|
|
441
|
+
};
|
|
442
|
+
// Timeout fallback is dynamic: the orchestrator now sends a
|
|
443
|
+
// challenge-specific message ("No pudimos reconocer el giro hacia
|
|
444
|
+
// la izquierda"), but if it ever drops it the SDK rebuilds the
|
|
445
|
+
// same text from the locally tracked active challenge.
|
|
446
|
+
let timeoutFallback: string | undefined;
|
|
447
|
+
if (msg.code === 'CHALLENGE_TIMEOUT') {
|
|
448
|
+
const ch = currentChallengeRef.current;
|
|
449
|
+
switch (ch) {
|
|
450
|
+
case 'TURN_LEFT': timeoutFallback = t.liveness.timeoutTurnLeft; break;
|
|
451
|
+
case 'TURN_RIGHT': timeoutFallback = t.liveness.timeoutTurnRight; break;
|
|
452
|
+
case 'BLINK': timeoutFallback = t.liveness.timeoutBlink; break;
|
|
453
|
+
case 'SMILE': timeoutFallback = t.liveness.timeoutSmile; break;
|
|
454
|
+
case 'MOVE_CLOSER': timeoutFallback = t.liveness.timeoutMoveCloser; break;
|
|
455
|
+
case 'MOVE_FARTHER': timeoutFallback = t.liveness.timeoutMoveFarther; break;
|
|
456
|
+
default: timeoutFallback = t.liveness.timeoutGeneric;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const codedFallback =
|
|
460
|
+
timeoutFallback ??
|
|
461
|
+
(msg.code ? FALLBACK_BY_CODE[msg.code] : undefined) ??
|
|
462
|
+
t.liveness.warningGeneric;
|
|
463
|
+
// Prefer the server-localised message — it's situationally
|
|
464
|
+
// accurate ("El fondo tiene mucho movimiento o reflejos. Pasá a
|
|
465
|
+
// un fondo neutro y plano"). Drop to the SDK fallback only when
|
|
466
|
+
// the orchestrator did not provide one.
|
|
467
|
+
const baseText = msg.message ?? codedFallback;
|
|
468
|
+
const text = isPositioningReady
|
|
469
|
+
? t.liveness.positioningReady
|
|
470
|
+
: typeof msg.consecutiveMisses === 'number' &&
|
|
471
|
+
msg.consecutiveMisses >= 3
|
|
472
|
+
? t.liveness.warningConsecutiveMisses
|
|
473
|
+
.replace('{n}', String(msg.consecutiveMisses))
|
|
474
|
+
.concat(' — ', baseText)
|
|
475
|
+
: baseText;
|
|
476
|
+
showToast(isPositioningReady ? 'success' : 'warning', text);
|
|
477
|
+
resetWatchdog();
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
case 'ERROR': {
|
|
481
|
+
failWith({
|
|
482
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
483
|
+
message:
|
|
484
|
+
msg.message ?? msg.code ?? 'Iris reported an error during liveness.',
|
|
485
|
+
});
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
case 'SESSION_COMPLETE': {
|
|
489
|
+
stopWatchdog();
|
|
490
|
+
if (finalizingRef.current) return;
|
|
491
|
+
finalizingRef.current = true;
|
|
492
|
+
stopCapture();
|
|
493
|
+
try {
|
|
494
|
+
wsRef.current?.close(1000, 'session_complete');
|
|
495
|
+
} catch {
|
|
496
|
+
/* ignore */
|
|
497
|
+
}
|
|
498
|
+
wsRef.current = null;
|
|
499
|
+
setPhase('finalizing');
|
|
500
|
+
(async () => {
|
|
501
|
+
try {
|
|
502
|
+
await api.post(`/sessions/${sessionId}/liveness-verify`, {});
|
|
503
|
+
onComplete();
|
|
504
|
+
} catch (err) {
|
|
505
|
+
const message =
|
|
506
|
+
err && typeof err === 'object' && 'message' in err
|
|
507
|
+
? String((err as { message?: unknown }).message)
|
|
508
|
+
: 'Failed to notify the backend that liveness completed.';
|
|
509
|
+
failWith({
|
|
510
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
511
|
+
message,
|
|
512
|
+
cause: err,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
})();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
default:
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
[
|
|
523
|
+
adjustFps,
|
|
524
|
+
api,
|
|
525
|
+
challengeLabel,
|
|
526
|
+
failWith,
|
|
527
|
+
onComplete,
|
|
528
|
+
presentTerminalRetry,
|
|
529
|
+
resetWatchdog,
|
|
530
|
+
sessionId,
|
|
531
|
+
showToast,
|
|
532
|
+
startCaptureLoop,
|
|
533
|
+
stopCapture,
|
|
534
|
+
stopWatchdog,
|
|
535
|
+
t.liveness.getReady,
|
|
536
|
+
t.liveness.positioningReady,
|
|
537
|
+
t.liveness.warningConsecutiveMisses,
|
|
538
|
+
t.liveness.warningGeneric,
|
|
539
|
+
],
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const startVerification = useCallback(async () => {
|
|
543
|
+
setBannerError(null);
|
|
544
|
+
setRetry(null);
|
|
545
|
+
setStatusText(t.liveness.connecting);
|
|
546
|
+
finalizingRef.current = false;
|
|
547
|
+
framesSentRef.current = 0;
|
|
548
|
+
currentChallengeRef.current = null;
|
|
549
|
+
lastChallengeMetaRef.current = null;
|
|
550
|
+
frameMsRef.current = FRAME_MS_DEFAULT;
|
|
551
|
+
hmacRef.current = null;
|
|
552
|
+
|
|
553
|
+
let init: LivenessSessionResponse;
|
|
554
|
+
try {
|
|
555
|
+
// `calibration_label` is the wire name expected by the backend
|
|
556
|
+
// (snake_case mirroring iris-test's `/sessions` POST). Only sent when
|
|
557
|
+
// the host explicitly enables calibration via VelionConfig.
|
|
558
|
+
const body: Record<string, unknown> = {};
|
|
559
|
+
if (calibrationLabel && calibrationLabel.length > 0) {
|
|
560
|
+
body.calibration_label = calibrationLabel;
|
|
561
|
+
}
|
|
562
|
+
init = await api.post<LivenessSessionResponse>(
|
|
563
|
+
`/sessions/${sessionId}/liveness-init`,
|
|
564
|
+
body,
|
|
565
|
+
);
|
|
566
|
+
} catch (err) {
|
|
567
|
+
failWith({
|
|
568
|
+
code: SdkErrorCode.LIVENESS_INIT_FAILED,
|
|
569
|
+
message:
|
|
570
|
+
(err && typeof err === 'object' && 'message' in err
|
|
571
|
+
? String((err as { message?: unknown }).message)
|
|
572
|
+
: '') || 'Failed to create the Iris liveness session.',
|
|
573
|
+
cause: err,
|
|
574
|
+
});
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (!init.liveness_session_id || !init.websocket_url) {
|
|
578
|
+
failWith({
|
|
579
|
+
code: SdkErrorCode.LIVENESS_INIT_FAILED,
|
|
580
|
+
message: 'liveness-init response missing required fields.',
|
|
581
|
+
});
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
livenessSessionIdRef.current = init.liveness_session_id;
|
|
585
|
+
if (init.frame_integrity_key_base64) {
|
|
586
|
+
try {
|
|
587
|
+
hmacRef.current = importHmacKey(init.frame_integrity_key_base64);
|
|
588
|
+
} catch (err) {
|
|
589
|
+
log.warn('[Liveness] failed to import HMAC key', err);
|
|
590
|
+
hmacRef.current = null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
setStatusText(t.liveness.preparingCamera);
|
|
595
|
+
let connection: IrisConnection;
|
|
596
|
+
try {
|
|
597
|
+
connection = openIrisWebSocket({
|
|
598
|
+
websocketUrl: init.websocket_url,
|
|
599
|
+
connectionToken: init.connection_token,
|
|
600
|
+
locale,
|
|
601
|
+
onOpen: () => setStatusText(t.liveness.connecting),
|
|
602
|
+
onMessage: handleWsMessage,
|
|
603
|
+
onClose: (code, reason) => {
|
|
604
|
+
if (finalizingRef.current) return;
|
|
605
|
+
// Code 4001 is the close we send ourselves from
|
|
606
|
+
// `presentTerminalRetry` to tear down the WS before showing the
|
|
607
|
+
// retry dialog. Checking the React state `retry` here is unsafe
|
|
608
|
+
// — this handler is registered via closure on a stale render,
|
|
609
|
+
// so `retry` reads `null` even when we just called `setRetry`.
|
|
610
|
+
// The close code is reliable: we always pair 4001 with the
|
|
611
|
+
// retry-dialog mount.
|
|
612
|
+
if (code === 4001) return;
|
|
613
|
+
stopCapture();
|
|
614
|
+
stopWatchdog();
|
|
615
|
+
if (code === 1000) return;
|
|
616
|
+
failWith({
|
|
617
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
618
|
+
message: `Iris WebSocket closed before completion (code=${code}${
|
|
619
|
+
reason ? `, reason=${reason}` : ''
|
|
620
|
+
}).`,
|
|
621
|
+
});
|
|
622
|
+
},
|
|
623
|
+
});
|
|
624
|
+
} catch (err) {
|
|
625
|
+
failWith({
|
|
626
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
627
|
+
message:
|
|
628
|
+
err instanceof Error
|
|
629
|
+
? err.message
|
|
630
|
+
: 'Failed to open the Iris WebSocket.',
|
|
631
|
+
cause: err,
|
|
632
|
+
});
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
wsRef.current = connection;
|
|
636
|
+
}, [
|
|
637
|
+
api,
|
|
638
|
+
calibrationLabel,
|
|
639
|
+
failWith,
|
|
640
|
+
handleWsMessage,
|
|
641
|
+
locale,
|
|
642
|
+
retry,
|
|
643
|
+
sessionId,
|
|
644
|
+
stopCapture,
|
|
645
|
+
stopWatchdog,
|
|
646
|
+
t.liveness.connecting,
|
|
647
|
+
t.liveness.preparingCamera,
|
|
648
|
+
]);
|
|
649
|
+
|
|
650
|
+
// Re-run after a terminal retry — same cleanup as start, but reuses the
|
|
651
|
+
// mounted camera (no DOM teardown).
|
|
652
|
+
const restartVerification = useCallback(async () => {
|
|
653
|
+
setRetry(null);
|
|
654
|
+
setProgress(null);
|
|
655
|
+
setChallengeText(null);
|
|
656
|
+
setOvalCue('NEUTRAL');
|
|
657
|
+
await startVerification();
|
|
658
|
+
}, [startVerification]);
|
|
659
|
+
|
|
660
|
+
// Effect: when the user enters the capture phase (intro skipped or
|
|
661
|
+
// confirmed), run the start sequence.
|
|
662
|
+
useEffect(() => {
|
|
663
|
+
if (phase !== 'capturing') return;
|
|
664
|
+
void startVerification();
|
|
665
|
+
return () => {
|
|
666
|
+
stopCapture();
|
|
667
|
+
stopWatchdog();
|
|
668
|
+
if (toastTimerRef.current) clearTimeout(toastTimerRef.current);
|
|
669
|
+
try {
|
|
670
|
+
wsRef.current?.close(1000, 'unmount');
|
|
671
|
+
} catch {
|
|
672
|
+
/* ignore */
|
|
673
|
+
}
|
|
674
|
+
wsRef.current = null;
|
|
675
|
+
};
|
|
676
|
+
// We intentionally only re-run when phase flips into capturing — the
|
|
677
|
+
// first start; the retry path is handled by `restartVerification`.
|
|
678
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
679
|
+
}, [phase]);
|
|
680
|
+
|
|
681
|
+
if (retry) {
|
|
682
|
+
return (
|
|
683
|
+
<RetryDialog
|
|
684
|
+
title={t.liveness.terminalRetryTitle}
|
|
685
|
+
message={retry.message}
|
|
686
|
+
retryLabel={t.liveness.terminalRetryButton}
|
|
687
|
+
cancelLabel={t.liveness.terminalCancelButton}
|
|
688
|
+
onRetry={() => {
|
|
689
|
+
void restartVerification();
|
|
690
|
+
}}
|
|
691
|
+
onCancel={() => {
|
|
692
|
+
setRetry(null);
|
|
693
|
+
onCancel({
|
|
694
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
695
|
+
message: retry.message,
|
|
696
|
+
});
|
|
697
|
+
}}
|
|
698
|
+
/>
|
|
699
|
+
);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (phase === 'intro') {
|
|
703
|
+
return (
|
|
704
|
+
<View style={{ flex: 1 }}>
|
|
705
|
+
<StepShell
|
|
706
|
+
title={t.liveness.introTitle}
|
|
707
|
+
subtitle={t.liveness.introSubtitle}
|
|
708
|
+
iconNode={<StepIcon name="face" />}
|
|
709
|
+
>
|
|
710
|
+
<ScrollView contentContainerStyle={styles.tipsList}>
|
|
711
|
+
{t.liveness.introTips.map((tip, idx) => (
|
|
712
|
+
<View
|
|
713
|
+
key={idx}
|
|
714
|
+
style={[
|
|
715
|
+
styles.tipRow,
|
|
716
|
+
{
|
|
717
|
+
borderColor: theme.borderColor,
|
|
718
|
+
backgroundColor: mixHex(
|
|
719
|
+
theme.primaryColor,
|
|
720
|
+
theme.backgroundColor,
|
|
721
|
+
6,
|
|
722
|
+
),
|
|
723
|
+
borderRadius: theme.borderRadiusPx,
|
|
724
|
+
},
|
|
725
|
+
]}
|
|
726
|
+
>
|
|
727
|
+
<View
|
|
728
|
+
style={[
|
|
729
|
+
styles.tipIcon,
|
|
730
|
+
{
|
|
731
|
+
backgroundColor: mixHex(
|
|
732
|
+
theme.primaryColor,
|
|
733
|
+
theme.backgroundColor,
|
|
734
|
+
18,
|
|
735
|
+
),
|
|
736
|
+
},
|
|
737
|
+
]}
|
|
738
|
+
>
|
|
739
|
+
<Text
|
|
740
|
+
style={[styles.tipIconText, { color: theme.primaryColor }]}
|
|
741
|
+
>
|
|
742
|
+
{idx + 1}
|
|
743
|
+
</Text>
|
|
744
|
+
</View>
|
|
745
|
+
<Text style={[styles.tipText, { color: theme.textColor }]}>
|
|
746
|
+
{tip}
|
|
747
|
+
</Text>
|
|
748
|
+
</View>
|
|
749
|
+
))}
|
|
750
|
+
</ScrollView>
|
|
751
|
+
{bannerError ? <Banner message={bannerError} /> : null}
|
|
752
|
+
<View style={styles.introActions}>
|
|
753
|
+
{showBackButton ? (
|
|
754
|
+
<Button
|
|
755
|
+
variant="ghost"
|
|
756
|
+
label={t.liveness.introBack}
|
|
757
|
+
onPress={() =>
|
|
758
|
+
onCancel({
|
|
759
|
+
code: SdkErrorCode.LIVENESS_FAILED,
|
|
760
|
+
message: 'User exited the liveness intro.',
|
|
761
|
+
})
|
|
762
|
+
}
|
|
763
|
+
fullWidth={false}
|
|
764
|
+
style={styles.introBtn}
|
|
765
|
+
/>
|
|
766
|
+
) : null}
|
|
767
|
+
<Button
|
|
768
|
+
label={t.liveness.introStart}
|
|
769
|
+
onPress={() => setPhase('capturing')}
|
|
770
|
+
fullWidth={!showBackButton}
|
|
771
|
+
style={showBackButton ? styles.introBtn : undefined}
|
|
772
|
+
/>
|
|
773
|
+
</View>
|
|
774
|
+
</StepShell>
|
|
775
|
+
</View>
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (phase === 'finalizing') {
|
|
780
|
+
return (
|
|
781
|
+
<AnalyzingOverlay
|
|
782
|
+
headline={t.liveness.analyzingHeadline}
|
|
783
|
+
caption={t.liveness.analyzingCaption}
|
|
784
|
+
/>
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return (
|
|
789
|
+
<View style={{ flex: 1 }}>
|
|
790
|
+
<LivenessCamera
|
|
791
|
+
ref={cameraRef}
|
|
792
|
+
statusText={statusText}
|
|
793
|
+
challengeText={challengeText}
|
|
794
|
+
ovalCue={ovalCue}
|
|
795
|
+
toast={toast}
|
|
796
|
+
progress={progress}
|
|
797
|
+
permissionDeniedLabel={t.liveness.cameraPermissionDenied}
|
|
798
|
+
retryLabel={t.liveness.cameraTryAgain}
|
|
799
|
+
cancelLabel={t.common.cancel}
|
|
800
|
+
// Intentionally no onCancel during capture. The capture screen is
|
|
801
|
+
// meant to be fullscreen and uninterruptible — if the user wants
|
|
802
|
+
// out, the 25 s progress watchdog catches stalled sessions and
|
|
803
|
+
// routes them through the RetryDialog (with its own Cancel option).
|
|
804
|
+
// The intro screen keeps the explicit "Atrás" button.
|
|
805
|
+
/>
|
|
806
|
+
</View>
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
const styles = StyleSheet.create({
|
|
811
|
+
tipsList: {
|
|
812
|
+
paddingVertical: 4,
|
|
813
|
+
gap: 10,
|
|
814
|
+
},
|
|
815
|
+
tipRow: {
|
|
816
|
+
flexDirection: 'row',
|
|
817
|
+
alignItems: 'center',
|
|
818
|
+
gap: 12,
|
|
819
|
+
padding: 12,
|
|
820
|
+
borderWidth: 1,
|
|
821
|
+
},
|
|
822
|
+
tipIcon: {
|
|
823
|
+
width: 32,
|
|
824
|
+
height: 32,
|
|
825
|
+
borderRadius: 16,
|
|
826
|
+
alignItems: 'center',
|
|
827
|
+
justifyContent: 'center',
|
|
828
|
+
},
|
|
829
|
+
tipIconText: {
|
|
830
|
+
fontSize: 14,
|
|
831
|
+
fontWeight: '700',
|
|
832
|
+
},
|
|
833
|
+
tipText: {
|
|
834
|
+
flex: 1,
|
|
835
|
+
fontSize: 14,
|
|
836
|
+
lineHeight: 20,
|
|
837
|
+
},
|
|
838
|
+
introActions: {
|
|
839
|
+
flexDirection: 'row',
|
|
840
|
+
gap: 10,
|
|
841
|
+
marginTop: 6,
|
|
842
|
+
},
|
|
843
|
+
introBtn: {
|
|
844
|
+
flex: 1,
|
|
845
|
+
},
|
|
846
|
+
});
|