@smileid/usesmileid 12.0.0 → 12.0.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 +8 -0
- package/README.md +1 -1
- package/dist/camera/camera_defaults.d.ts +2 -0
- package/dist/camera/camera_defaults.d.ts.map +1 -1
- package/dist/camera/camera_defaults.js +2 -0
- package/dist/camera/camera_defaults.js.map +1 -1
- package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.d.ts +11 -0
- package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.d.ts.map +1 -0
- package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.js +17 -0
- package/dist/core/exception/agent_mode_with_enhanced_liveness_exception.js.map +1 -0
- package/dist/core/exception/capture_validation_exceptions.d.ts +1 -0
- package/dist/core/exception/capture_validation_exceptions.d.ts.map +1 -1
- package/dist/core/exception/capture_validation_exceptions.js +1 -0
- package/dist/core/exception/capture_validation_exceptions.js.map +1 -1
- package/dist/core/exception/flow_exceptions.d.ts +4 -2
- package/dist/core/exception/flow_exceptions.d.ts.map +1 -1
- package/dist/core/exception/flow_exceptions.js +3 -2
- package/dist/core/exception/flow_exceptions.js.map +1 -1
- package/dist/core/exception/index.d.ts +1 -0
- package/dist/core/exception/index.d.ts.map +1 -1
- package/dist/core/exception/index.js +1 -0
- package/dist/core/exception/index.js.map +1 -1
- package/dist/core/exception/missing_job_type_exception.d.ts +1 -3
- package/dist/core/exception/missing_job_type_exception.d.ts.map +1 -1
- package/dist/core/exception/missing_job_type_exception.js +2 -4
- package/dist/core/exception/missing_job_type_exception.js.map +1 -1
- package/dist/core/exception/use_smile_id_error_code.d.ts +1 -0
- package/dist/core/exception/use_smile_id_error_code.d.ts.map +1 -1
- package/dist/core/exception/use_smile_id_error_code.js +1 -0
- package/dist/core/exception/use_smile_id_error_code.js.map +1 -1
- package/dist/core/models/config.d.ts +19 -0
- package/dist/core/models/config.d.ts.map +1 -1
- package/dist/core/models/document_type.d.ts +1 -1
- package/dist/core/models/document_type.d.ts.map +1 -1
- package/dist/core/models/document_type.js +2 -2
- package/dist/core/models/document_type.js.map +1 -1
- package/dist/core/models/global_privacy_control.d.ts +36 -0
- package/dist/core/models/global_privacy_control.d.ts.map +1 -0
- package/dist/core/models/global_privacy_control.js +39 -0
- package/dist/core/models/global_privacy_control.js.map +1 -0
- package/dist/core/models/network_config.d.ts +2 -2
- package/dist/core/models/network_config.js +2 -2
- package/dist/data/api/interceptors/smile_auth_interceptor.d.ts.map +1 -1
- package/dist/data/api/interceptors/smile_auth_interceptor.js +3 -17
- package/dist/data/api/interceptors/smile_auth_interceptor.js.map +1 -1
- package/dist/data/api/smile_id_api.d.ts +5 -1
- package/dist/data/api/smile_id_api.d.ts.map +1 -1
- package/dist/data/api/smile_id_api.js +30 -27
- package/dist/data/api/smile_id_api.js.map +1 -1
- package/dist/data/dsl/builder/retry_config_builder.d.ts +4 -4
- package/dist/data/dsl/builder/retry_config_builder.d.ts.map +1 -1
- package/dist/data/dsl/builder/retry_config_builder.js +6 -6
- package/dist/data/dsl/builder/retry_config_builder.js.map +1 -1
- package/dist/data/model/job_request.d.ts +28 -12
- package/dist/data/model/job_request.d.ts.map +1 -1
- package/dist/data/model/token_payload.d.ts +78 -0
- package/dist/data/model/token_payload.d.ts.map +1 -0
- package/dist/data/model/token_payload.js +132 -0
- package/dist/data/model/token_payload.js.map +1 -0
- package/dist/data/model/user_details_payload.d.ts +39 -0
- package/dist/data/model/user_details_payload.d.ts.map +1 -0
- package/dist/data/model/user_details_payload.js +53 -0
- package/dist/data/model/user_details_payload.js.map +1 -0
- package/dist/data/smile_id_urls.d.ts +1 -1
- package/dist/data/smile_id_urls.js +1 -1
- package/dist/data/smile_id_urls.js.map +1 -1
- package/dist/data/util/retry_util.d.ts +1 -1
- package/dist/data/util/retry_util.d.ts.map +1 -1
- package/dist/data/util/retry_util.js +5 -0
- package/dist/data/util/retry_util.js.map +1 -1
- package/dist/data/util/use_smile_id_token_decoder.d.ts +15 -3
- package/dist/data/util/use_smile_id_token_decoder.d.ts.map +1 -1
- package/dist/data/util/use_smile_id_token_decoder.js +20 -8
- package/dist/data/util/use_smile_id_token_decoder.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/l10n/strings_en.d.ts +4 -1
- package/dist/l10n/strings_en.d.ts.map +1 -1
- package/dist/l10n/strings_en.js +4 -1
- package/dist/l10n/strings_en.js.map +1 -1
- package/dist/l10n/use_smile_id_localizations.d.ts +10 -0
- package/dist/l10n/use_smile_id_localizations.d.ts.map +1 -1
- package/dist/l10n/use_smile_id_localizations.js +59 -3
- package/dist/l10n/use_smile_id_localizations.js.map +1 -1
- package/dist/metadata/builder_configuration.d.ts +5 -0
- package/dist/metadata/builder_configuration.d.ts.map +1 -1
- package/dist/metadata/builder_configuration.js +1 -0
- package/dist/metadata/builder_configuration.js.map +1 -1
- package/dist/metadata/metadata_configuration_mappers.d.ts +2 -3
- package/dist/metadata/metadata_configuration_mappers.d.ts.map +1 -1
- package/dist/metadata/metadata_configuration_mappers.js +13 -6
- package/dist/metadata/metadata_configuration_mappers.js.map +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.d.ts.map +1 -1
- package/dist/metadata/use_smile_id_metadata_factory.js +14 -5
- package/dist/metadata/use_smile_id_metadata_factory.js.map +1 -1
- package/dist/presentation/components/use_smile_id_button.d.ts.map +1 -1
- package/dist/presentation/components/use_smile_id_button.js +8 -2
- package/dist/presentation/components/use_smile_id_button.js.map +1 -1
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts +5 -2
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts.map +1 -1
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js +8 -3
- package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js.map +1 -1
- package/dist/presentation/components/use_smile_id_instructions_hero.d.ts.map +1 -1
- package/dist/presentation/components/use_smile_id_instructions_hero.js +8 -3
- package/dist/presentation/components/use_smile_id_instructions_hero.js.map +1 -1
- package/dist/presentation/flow/dsl/builder/config_builder.d.ts +16 -0
- package/dist/presentation/flow/dsl/builder/config_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/builder/config_builder.js +18 -0
- package/dist/presentation/flow/dsl/builder/config_builder.js.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts +6 -12
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.js +7 -12
- package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +23 -1
- package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts +22 -3
- package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.js +84 -46
- package/dist/presentation/flow/flow_navigation_manager.js.map +1 -1
- package/dist/presentation/flow/model/instructions_defaults.d.ts.map +1 -1
- package/dist/presentation/flow/model/instructions_defaults.js +1 -1
- package/dist/presentation/flow/model/instructions_defaults.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_capture_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_capture_screen.js +13 -3
- package/dist/presentation/flow/screen/orchestrated_capture_screen.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_consent_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_consent_screen.js +3 -1
- package/dist/presentation/flow/screen/orchestrated_consent_screen.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_preview_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_preview_screen.js +0 -8
- package/dist/presentation/flow/screen/orchestrated_preview_screen.js.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.d.ts.map +1 -1
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js +25 -5
- package/dist/presentation/flow/screen/orchestrated_processing_screen.js.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.d.ts.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.js +45 -12
- package/dist/presentation/flow/use_smile_id_flow.js.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.d.ts +16 -2
- package/dist/presentation/flow/validation/flow_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.js +61 -47
- package/dist/presentation/flow/validation/flow_validator.js.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.d.ts +7 -0
- package/dist/presentation/flow/validation/job_type_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/job_type_validator.js +52 -27
- package/dist/presentation/flow/validation/job_type_validator.js.map +1 -1
- package/dist/presentation/flow/validation/user_details_validator.d.ts +9 -0
- package/dist/presentation/flow/validation/user_details_validator.d.ts.map +1 -0
- package/dist/presentation/flow/validation/user_details_validator.js +40 -0
- package/dist/presentation/flow/validation/user_details_validator.js.map +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -0
- package/dist/presentation/screen/helpers/capture_screen_helpers.js.map +1 -1
- package/dist/presentation/screen/helpers/capture_ui_state.d.ts +6 -6
- package/dist/presentation/screen/helpers/capture_ui_state.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/capture_ui_state.js +19 -19
- package/dist/presentation/screen/helpers/capture_ui_state.js.map +1 -1
- package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts +2 -2
- package/dist/presentation/screen/helpers/ess_turn_haptics.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/ess_turn_haptics.js +6 -6
- package/dist/presentation/screen/helpers/ess_turn_haptics.js.map +1 -1
- package/dist/presentation/screen/helpers/scan_state_to_overlay_state.d.ts +5 -5
- package/dist/presentation/screen/helpers/scan_state_to_overlay_state.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js +11 -11
- package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js.map +1 -1
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts +13 -3
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.d.ts.map +1 -1
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.js +48 -6
- package/dist/presentation/screen/helpers/use_capture_orientation_lock.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.d.ts +4 -2
- package/dist/presentation/screen/use_smile_id_capture_screen.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_capture_screen.js +90 -23
- package/dist/presentation/screen/use_smile_id_capture_screen.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_consent_screen.d.ts +7 -0
- package/dist/presentation/screen/use_smile_id_consent_screen.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_consent_screen.js +6 -5
- package/dist/presentation/screen/use_smile_id_consent_screen.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_document_back_instructions.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_document_back_instructions.js +5 -13
- package/dist/presentation/screen/use_smile_id_document_back_instructions.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_document_preview_screen.js +1 -1
- package/dist/presentation/screen/use_smile_id_document_preview_screen.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_preview_screen.d.ts +1 -21
- package/dist/presentation/screen/use_smile_id_preview_screen.d.ts.map +1 -1
- package/dist/presentation/screen/use_smile_id_preview_screen.js +3 -14
- package/dist/presentation/screen/use_smile_id_preview_screen.js.map +1 -1
- package/dist/presentation/screen/use_smile_id_processing_screen.js +1 -1
- package/dist/presentation/screen/use_smile_id_processing_screen.js.map +1 -1
- package/dist/presentation/utils/locale_utils.d.ts +10 -4
- package/dist/presentation/utils/locale_utils.d.ts.map +1 -1
- package/dist/presentation/utils/locale_utils.js +13 -5
- package/dist/presentation/utils/locale_utils.js.map +1 -1
- package/dist/testing/fake_face_analyzer.d.ts +43 -0
- package/dist/testing/fake_face_analyzer.d.ts.map +1 -0
- package/dist/testing/fake_face_analyzer.js +52 -0
- package/dist/testing/fake_face_analyzer.js.map +1 -0
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +7 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/scenario_expectations.d.ts +28 -0
- package/dist/testing/scenario_expectations.d.ts.map +1 -0
- package/dist/testing/scenario_expectations.js +65 -0
- package/dist/testing/scenario_expectations.js.map +1 -0
- package/dist/testing/scenario_loader.d.ts +11 -0
- package/dist/testing/scenario_loader.d.ts.map +1 -0
- package/dist/testing/scenario_loader.js +64 -0
- package/dist/testing/scenario_loader.js.map +1 -0
- package/dist/testing/scripted_scan_results.d.ts +17 -0
- package/dist/testing/scripted_scan_results.d.ts.map +1 -0
- package/dist/testing/scripted_scan_results.js +52 -0
- package/dist/testing/scripted_scan_results.js.map +1 -0
- package/package.json +11 -4
- package/src/__tests__/dsl.test.ts +51 -1
- package/src/__tests__/react_native_mock.tsx +22 -0
- package/src/__tests__/setup_react_native.ts +17 -2
- package/src/__tests__/snapshots/__snapshots__/screens_structural.test.tsx.snap +5267 -0
- package/src/__tests__/snapshots/screens_structural.test.tsx +230 -0
- package/src/camera/__tests__/camera_permissions.test.ts +72 -0
- package/src/camera/__tests__/camera_system_focus.test.ts +173 -0
- package/src/camera/__tests__/use_smile_id_camera_preview.test.tsx +461 -0
- package/src/camera/__tests__/use_smile_id_camera_system.test.ts +103 -0
- package/src/camera/__tests__/use_smile_id_frame_stream_hook.test.tsx +229 -0
- package/src/camera/camera_defaults.ts +3 -0
- package/src/core/exception/__tests__/builder_validation_exceptions.test.ts +81 -0
- package/src/core/exception/agent_mode_with_enhanced_liveness_exception.ts +20 -0
- package/src/core/exception/capture_validation_exceptions.ts +1 -0
- package/src/core/exception/flow_exceptions.test.ts +10 -0
- package/src/core/exception/flow_exceptions.ts +3 -2
- package/src/core/exception/index.ts +1 -0
- package/src/core/exception/missing_job_type_exception.ts +2 -4
- package/src/core/exception/use_smile_id_error_code.ts +1 -0
- package/src/core/models/__tests__/document_type.test.ts +2 -2
- package/src/core/models/__tests__/flow_models.test.ts +26 -0
- package/src/core/models/config.ts +22 -0
- package/src/core/models/document_type.ts +2 -2
- package/src/core/models/global_privacy_control.ts +45 -0
- package/src/core/models/network_config.ts +2 -2
- package/src/data/api/__tests__/smile_id_api.test.ts +275 -1
- package/src/data/api/interceptors/__tests__/credential_header_redaction.test.ts +144 -0
- package/src/data/api/interceptors/__tests__/gzip_interceptor.test.ts +83 -0
- package/src/data/api/interceptors/smile_auth_interceptor.ts +3 -18
- package/src/data/api/smile_id_api.ts +36 -49
- package/src/data/compression/__tests__/payload_compressor_real_jpeg.test.ts +6 -2
- package/src/data/dsl/__tests__/use_smile_id_network_builder.test.ts +157 -0
- package/src/data/dsl/builder/__tests__/config_builders.test.ts +62 -0
- package/src/data/dsl/builder/retry_config_builder.ts +6 -6
- package/src/data/factory/__tests__/retry_gzip_mac_composition.test.ts +163 -0
- package/src/data/factory/__tests__/signed_job_submission_headers.test.ts +1 -1
- package/src/data/model/__tests__/token_payload.test.ts +224 -0
- package/src/data/model/__tests__/user_details_payload.test.ts +100 -0
- package/src/data/model/job_request.ts +27 -12
- package/src/data/model/token_payload.ts +182 -0
- package/src/data/model/user_details_payload.ts +84 -0
- package/src/data/repository/__tests__/network_retries.test.ts +1 -1
- package/src/data/repository/__tests__/repository_dispatch.test.ts +366 -0
- package/src/data/repository/__tests__/use_smile_id_repository.test.ts +1 -1
- package/src/data/security/__tests__/request_canonicalizer.test.ts +132 -0
- package/src/data/security/__tests__/signing_crypto.test.ts +203 -0
- package/src/data/smile_id_urls.ts +1 -1
- package/src/data/util/__tests__/retry_util.test.ts +25 -0
- package/src/data/util/__tests__/use_smile_id_token_decoder.test.ts +179 -0
- package/src/data/util/retry_util.ts +9 -1
- package/src/data/util/use_smile_id_token_decoder.ts +29 -8
- package/src/index.ts +6 -4
- package/src/l10n/__tests__/use_smile_id_localizations.test.ts +122 -5
- package/src/l10n/__tests__/use_smile_id_localizations_provider.test.tsx +112 -0
- package/src/l10n/__tests__/use_smile_id_localizations_surface.test.ts +231 -0
- package/src/l10n/strings_en.ts +5 -1
- package/src/l10n/use_smile_id_localizations.ts +80 -3
- package/src/metadata/__tests__/device_orientation_sampling.test.ts +165 -0
- package/src/metadata/__tests__/geolocation.test.ts +150 -0
- package/src/metadata/__tests__/metadata_configuration_mappers.test.ts +39 -0
- package/src/metadata/__tests__/metadata_configuration_objects.test.ts +2 -1
- package/src/metadata/__tests__/metadata_device_context.test.ts +3 -3
- package/src/metadata/__tests__/metadata_device_os_android.test.ts +18 -0
- package/src/metadata/__tests__/metadata_golden_fixture.json +1 -1
- package/src/metadata/__tests__/metadata_payload_golden.test.ts +4 -4
- package/src/metadata/__tests__/optional_native_modules.test.ts +234 -0
- package/src/metadata/__tests__/use_smile_id_metadata.test.ts +1 -1
- package/src/metadata/__tests__/use_smile_id_metadata_factory.test.ts +615 -0
- package/src/metadata/builder_configuration.ts +6 -0
- package/src/metadata/metadata_configuration_mappers.ts +21 -7
- package/src/metadata/use_smile_id_metadata_factory.ts +17 -6
- package/src/presentation/assets/si_document_green_book_hero_animation.json +1 -4668
- package/src/presentation/assets/si_document_hero_animation.json +1 -4799
- package/src/presentation/assets/si_document_hero_back_animation.json +1 -2039
- package/src/presentation/assets/si_document_passport_hero_animation.json +1 -14491
- package/src/presentation/assets/si_ess_all_directions_hero_animation.json +1 -0
- package/src/presentation/assets/si_ess_animation_all_directions.json +1 -6589
- package/src/presentation/assets/si_selfie_hero_animation.json +1 -1439
- package/src/presentation/assets/si_smile_to_capture_animation.json +1 -1439
- package/src/presentation/components/__tests__/use_smile_id_content_card.test.tsx +112 -0
- package/src/presentation/components/__tests__/use_smile_id_enhanced_smart_selfie_clip.test.tsx +9 -4
- package/src/presentation/components/__tests__/use_smile_id_face_oval_image.test.tsx +190 -0
- package/src/presentation/components/__tests__/use_smile_id_instructions_hero.test.tsx +44 -0
- package/src/presentation/components/__tests__/use_smile_id_learn_more_content.test.tsx +159 -0
- package/src/presentation/components/__tests__/use_smile_id_processing_center_indicator.test.tsx +88 -0
- package/src/presentation/components/use_smile_id_button.tsx +7 -2
- package/src/presentation/components/use_smile_id_enhanced_smart_selfie_clip.tsx +9 -3
- package/src/presentation/components/use_smile_id_instructions_hero.tsx +10 -0
- package/src/presentation/flow/__tests__/document_flow_navigation.test.ts +3 -3
- package/src/presentation/flow/__tests__/flow_navigation_manager.test.ts +372 -1
- package/src/presentation/flow/__tests__/use_smile_id_flow.test.tsx +651 -0
- package/src/presentation/flow/dsl/__tests__/use_smile_id_builder_component.test.tsx +120 -0
- package/src/presentation/flow/dsl/builder/__tests__/config_builder.test.ts +25 -0
- package/src/presentation/flow/dsl/builder/config_builder.ts +21 -0
- package/src/presentation/flow/dsl/builder/screens_builder.ts +7 -12
- package/src/presentation/flow/dsl/use_smile_id_flow_builder.ts +27 -1
- package/src/presentation/flow/flow_navigation_manager.ts +108 -93
- package/src/presentation/flow/graph/__tests__/screen_renderer.test.tsx +102 -0
- package/src/presentation/flow/model/instructions_defaults.ts +1 -1
- package/src/presentation/flow/screen/__tests__/orchestrated_capture_screen.test.tsx +836 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_consent_screen.test.tsx +165 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_instructions_screen.test.tsx +163 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_preview_screen.test.tsx +224 -0
- package/src/presentation/flow/screen/__tests__/orchestrated_processing_screen.test.tsx +338 -0
- package/src/presentation/flow/screen/orchestrated_capture_screen.tsx +18 -3
- package/src/presentation/flow/screen/orchestrated_consent_screen.tsx +5 -0
- package/src/presentation/flow/screen/orchestrated_preview_screen.tsx +0 -12
- package/src/presentation/flow/screen/orchestrated_processing_screen.tsx +42 -5
- package/src/presentation/flow/use_smile_id_flow.tsx +63 -12
- package/src/presentation/flow/validation/__tests__/flow_validator.test.ts +219 -1
- package/src/presentation/flow/validation/__tests__/job_type_validator.test.ts +200 -0
- package/src/presentation/flow/validation/flow_validator.ts +85 -57
- package/src/presentation/flow/validation/job_type_validator.ts +73 -39
- package/src/presentation/flow/validation/user_details_validator.ts +53 -0
- package/src/presentation/screen/__tests__/capture_ui_state.test.ts +66 -47
- package/src/presentation/screen/__tests__/ess_turn_haptics.test.ts +15 -11
- package/src/presentation/screen/__tests__/feedback_copy_table.test.ts +145 -0
- package/src/presentation/screen/__tests__/scan_state_to_overlay_state.test.ts +47 -27
- package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +524 -14
- package/src/presentation/screen/__tests__/use_smile_id_document_back_instructions.test.tsx +4 -2
- package/src/presentation/screen/__tests__/use_smile_id_permission_screen.test.tsx +68 -0
- package/src/presentation/screen/__tests__/use_smile_id_preview_screen.test.tsx +10 -40
- package/src/presentation/screen/helpers/__tests__/use_capture_orientation_lock.test.tsx +212 -0
- package/src/presentation/screen/helpers/capture_screen_helpers.ts +1 -0
- package/src/presentation/screen/helpers/capture_ui_state.ts +27 -27
- package/src/presentation/screen/helpers/ess_turn_haptics.ts +9 -9
- package/src/presentation/screen/helpers/scan_state_to_overlay_state.ts +13 -13
- package/src/presentation/screen/helpers/use_capture_orientation_lock.ts +69 -7
- package/src/presentation/screen/use_smile_id_capture_screen.tsx +107 -28
- package/src/presentation/screen/use_smile_id_consent_screen.tsx +16 -5
- package/src/presentation/screen/use_smile_id_document_back_instructions.tsx +14 -23
- package/src/presentation/screen/use_smile_id_document_preview_screen.tsx +1 -1
- package/src/presentation/screen/use_smile_id_preview_screen.tsx +13 -45
- package/src/presentation/screen/use_smile_id_processing_screen.tsx +1 -1
- package/src/presentation/theme/__tests__/use_use_smile_id_fonts.test.tsx +166 -0
- package/src/presentation/utils/__tests__/locale_utils.test.ts +46 -5
- package/src/presentation/utils/locale_utils.ts +13 -4
- package/src/testing/__tests__/fake_face_analyzer.test.ts +42 -0
- package/src/testing/__tests__/scenario_expectations.test.ts +110 -0
- package/src/testing/__tests__/scenario_loader.test.ts +64 -0
- package/src/testing/fake_face_analyzer.ts +82 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/scenario_expectations.ts +87 -0
- package/src/testing/scenario_loader.ts +83 -0
- package/src/testing/scripted_scan_results.ts +66 -0
- package/dist/presentation/components/use_smile_id_face_silhouette_overlay.d.ts +0 -15
- package/dist/presentation/components/use_smile_id_face_silhouette_overlay.d.ts.map +0 -1
- package/dist/presentation/components/use_smile_id_face_silhouette_overlay.js +0 -19
- package/dist/presentation/components/use_smile_id_face_silhouette_overlay.js.map +0 -1
- package/src/presentation/components/use_smile_id_face_silhouette_overlay.tsx +0 -27
|
@@ -18,13 +18,21 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
InvalidFieldValueException,
|
|
20
20
|
InvalidJobTypeScreenConfigException,
|
|
21
|
+
MissingJobTypeException,
|
|
21
22
|
MissingMLConfigurationBlockException,
|
|
22
23
|
} from "../../../../core/exception/job_validation_exceptions";
|
|
23
|
-
import { InvalidDocumentCaptureConfigException } from "../../../../core/exception/capture_validation_exceptions";
|
|
24
24
|
import {
|
|
25
|
+
AgentModeWithEnhancedLivenessException,
|
|
26
|
+
InvalidDocumentCaptureConfigException,
|
|
27
|
+
} from "../../../../core/exception/capture_validation_exceptions";
|
|
28
|
+
import {
|
|
29
|
+
runConsentValidation,
|
|
25
30
|
runDocumentVerificationParamsValidation,
|
|
26
31
|
runEnhancedDocumentVerificationParamsValidation,
|
|
32
|
+
runUserDetailsValidation,
|
|
27
33
|
} from "../flow_validator";
|
|
34
|
+
import { InvalidConsentInformationException } from "../../../../core/exception/consent_validation_exceptions";
|
|
35
|
+
import type { TokenPayload } from "../../../../data/model/token_payload";
|
|
28
36
|
|
|
29
37
|
function stubFaceAnalyzer(): FaceAnalyzer {
|
|
30
38
|
return {
|
|
@@ -189,6 +197,63 @@ describe("FlowValidator — document capture config", () => {
|
|
|
189
197
|
});
|
|
190
198
|
});
|
|
191
199
|
|
|
200
|
+
describe("FlowValidator — selfie capture config", () => {
|
|
201
|
+
function selfieCaptureIssues(
|
|
202
|
+
configure: (s: {
|
|
203
|
+
allowAgentMode: boolean;
|
|
204
|
+
enableEnhancedLiveness: boolean;
|
|
205
|
+
}) => void,
|
|
206
|
+
) {
|
|
207
|
+
const config = new CaptureConfigBuilder();
|
|
208
|
+
config.captureType = CaptureType.selfie;
|
|
209
|
+
config.selfie(configure);
|
|
210
|
+
return runFlowValidation({
|
|
211
|
+
steps: [{ kind: "capture" as const, config }, processingStep()],
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function agentModeIssues(
|
|
216
|
+
configure: (s: {
|
|
217
|
+
allowAgentMode: boolean;
|
|
218
|
+
enableEnhancedLiveness: boolean;
|
|
219
|
+
}) => void,
|
|
220
|
+
): AgentModeWithEnhancedLivenessException[] {
|
|
221
|
+
return selfieCaptureIssues(configure).filter(
|
|
222
|
+
(i): i is AgentModeWithEnhancedLivenessException =>
|
|
223
|
+
i instanceof AgentModeWithEnhancedLivenessException,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const rejectsAgentModePair = (
|
|
228
|
+
configure: Parameters<typeof agentModeIssues>[0],
|
|
229
|
+
): boolean => agentModeIssues(configure).length > 0;
|
|
230
|
+
|
|
231
|
+
it("rejects agent mode combined with enhanced liveness", () => {
|
|
232
|
+
const issues = agentModeIssues((s) => {
|
|
233
|
+
s.allowAgentMode = true;
|
|
234
|
+
s.enableEnhancedLiveness = true;
|
|
235
|
+
});
|
|
236
|
+
expect(issues).toHaveLength(1);
|
|
237
|
+
// Wire string partner tooling pivots on — a rename is a breaking change.
|
|
238
|
+
expect(issues[0]?.errorCode).toBe(
|
|
239
|
+
"BUILDER_AGENT_MODE_WITH_ENHANCED_LIVENESS",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("accepts agent mode and enhanced liveness on their own", () => {
|
|
244
|
+
expect(
|
|
245
|
+
rejectsAgentModePair((s) => {
|
|
246
|
+
s.allowAgentMode = true;
|
|
247
|
+
}),
|
|
248
|
+
).toBe(false);
|
|
249
|
+
expect(
|
|
250
|
+
rejectsAgentModePair((s) => {
|
|
251
|
+
s.enableEnhancedLiveness = true;
|
|
252
|
+
}),
|
|
253
|
+
).toBe(false);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
192
257
|
describe("FlowValidator — analyzer-per-capture-type", () => {
|
|
193
258
|
for (const { name, value: jobType } of SELFIE_JOB_TYPES) {
|
|
194
259
|
it(`${name}: missing ml block emits "is required" + MissingMLConfigurationBlockException`, () => {
|
|
@@ -396,3 +461,156 @@ describe("FlowValidator — document verification params", () => {
|
|
|
396
461
|
).toHaveLength(0);
|
|
397
462
|
});
|
|
398
463
|
});
|
|
464
|
+
|
|
465
|
+
describe("FlowValidator — jobType recognition", () => {
|
|
466
|
+
const ml: MLConfig = { analyzers: { selfie: stubFaceAnalyzer() } };
|
|
467
|
+
|
|
468
|
+
function jobTypeIssue(jobType: JobType) {
|
|
469
|
+
const issues = runFlowValidation({
|
|
470
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
471
|
+
ml,
|
|
472
|
+
network: validNetwork(jobType),
|
|
473
|
+
userDetails: validUserDetails,
|
|
474
|
+
});
|
|
475
|
+
return issues.find(
|
|
476
|
+
(i): i is MissingJobTypeException => i instanceof MissingJobTypeException,
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
it("rejects a jobType that is not one of the SDK's job types", () => {
|
|
481
|
+
expect(jobTypeIssue(99 as JobType)).toBeInstanceOf(MissingJobTypeException);
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("still rejects the unknown sentinel", () => {
|
|
485
|
+
expect(jobTypeIssue(JobType.unknown)).toBeInstanceOf(
|
|
486
|
+
MissingJobTypeException,
|
|
487
|
+
);
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("accepts every job type the SDK submits", () => {
|
|
491
|
+
for (const jobType of Object.values(JobType)) {
|
|
492
|
+
if (jobType === JobType.unknown) continue;
|
|
493
|
+
expect(jobTypeIssue(jobType)).toBeUndefined();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
describe("FlowValidator — token-provided user details (field-level relaxation)", () => {
|
|
499
|
+
function tokenPayload(overrides: Partial<TokenPayload> = {}): TokenPayload {
|
|
500
|
+
return {
|
|
501
|
+
hasGivenNames: false,
|
|
502
|
+
hasLastName: false,
|
|
503
|
+
hasEmail: false,
|
|
504
|
+
hasPhoneNumber: false,
|
|
505
|
+
...overrides,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
it("accepts contact-only userDetails when the token binds both names", () => {
|
|
510
|
+
const issues = runUserDetailsValidation(
|
|
511
|
+
{ givenNames: "", lastName: "", email: "ada@example.com" },
|
|
512
|
+
tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
513
|
+
);
|
|
514
|
+
expect(issues).toHaveLength(0);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it("accepts names-only userDetails when the token binds a contact field", () => {
|
|
518
|
+
const emailBound = runUserDetailsValidation(
|
|
519
|
+
{ givenNames: "Ada", lastName: "Lovelace" },
|
|
520
|
+
tokenPayload({ hasEmail: true }),
|
|
521
|
+
);
|
|
522
|
+
expect(emailBound).toHaveLength(0);
|
|
523
|
+
|
|
524
|
+
const phoneBound = runUserDetailsValidation(
|
|
525
|
+
{ givenNames: "Ada", lastName: "Lovelace" },
|
|
526
|
+
tokenPayload({ hasPhoneNumber: true }),
|
|
527
|
+
);
|
|
528
|
+
expect(phoneBound).toHaveLength(0);
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
it("relaxes per-field — a name the token does not bind still errors with today's message", () => {
|
|
532
|
+
const issues = runUserDetailsValidation(
|
|
533
|
+
{ givenNames: "", lastName: "", email: "ada@example.com" },
|
|
534
|
+
tokenPayload({ hasGivenNames: true }),
|
|
535
|
+
);
|
|
536
|
+
expect(issues).toHaveLength(1);
|
|
537
|
+
expect(issues[0]).toBeInstanceOf(InvalidFieldValueException);
|
|
538
|
+
expect(issues[0]?.message).toContain("userDetails.lastName");
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it("regression: without a token payload every existing rule fires unchanged", () => {
|
|
542
|
+
const issues = runUserDetailsValidation({ givenNames: "", lastName: "" });
|
|
543
|
+
expect(issues).toHaveLength(3);
|
|
544
|
+
const messages = issues.map((i) => i.message).join("\n");
|
|
545
|
+
expect(messages).toContain("userDetails.givenNames");
|
|
546
|
+
expect(messages).toContain("userDetails.lastName");
|
|
547
|
+
expect(messages).toContain(
|
|
548
|
+
"at least one of email or phoneNumber must be non blank",
|
|
549
|
+
);
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it("threads tokenPayload through runFlowValidation to the field rules", () => {
|
|
553
|
+
const issues = runFlowValidation({
|
|
554
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
555
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
556
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
557
|
+
userDetails: { givenNames: "", lastName: "", email: "ada@example.com" },
|
|
558
|
+
consentInformation: {
|
|
559
|
+
granted: true,
|
|
560
|
+
grantedAt: "2026-01-01T00:00:00.000Z",
|
|
561
|
+
noticeLanguage: "EN",
|
|
562
|
+
noticePrivacyPolicyUrl: "https://example.com",
|
|
563
|
+
},
|
|
564
|
+
tokenPayload: tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
565
|
+
});
|
|
566
|
+
expect(issues.some((i) => i instanceof InvalidFieldValueException)).toBe(
|
|
567
|
+
false,
|
|
568
|
+
);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it("a complete token consent skips content validation of the ignored preset", () => {
|
|
572
|
+
// Token data always wins: the preset is never sent, so its
|
|
573
|
+
// contents raise no issue even when they would fail on their own.
|
|
574
|
+
const issues = runFlowValidation({
|
|
575
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
576
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
577
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
578
|
+
userDetails: validUserDetails,
|
|
579
|
+
consentInformation: {
|
|
580
|
+
granted: false,
|
|
581
|
+
grantedAt: "",
|
|
582
|
+
noticeLanguage: "",
|
|
583
|
+
noticePrivacyPolicyUrl: "",
|
|
584
|
+
},
|
|
585
|
+
tokenPayload: {
|
|
586
|
+
...tokenPayload(),
|
|
587
|
+
consent: {
|
|
588
|
+
granted: true,
|
|
589
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
590
|
+
noticeLanguage: "EN",
|
|
591
|
+
noticePrivacyPolicyUrl: "https://partner.test/privacy",
|
|
592
|
+
},
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
expect(
|
|
596
|
+
issues.some((i) => i instanceof InvalidConsentInformationException),
|
|
597
|
+
).toBe(false);
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
it("the standalone pre-flight validator stays strict (no token context)", () => {
|
|
601
|
+
// `b.validateUserDetails(payload)` has no token context by design —
|
|
602
|
+
// partners pre-flight the payload they would assign, and blank
|
|
603
|
+
// names still fail there.
|
|
604
|
+
expect(
|
|
605
|
+
runUserDetailsValidation({ givenNames: "", lastName: "Lovelace" }),
|
|
606
|
+
).not.toHaveLength(0);
|
|
607
|
+
expect(
|
|
608
|
+
runConsentValidation({
|
|
609
|
+
granted: false,
|
|
610
|
+
grantedAt: "",
|
|
611
|
+
noticeLanguage: "",
|
|
612
|
+
noticePrivacyPolicyUrl: "",
|
|
613
|
+
}),
|
|
614
|
+
).not.toHaveLength(0);
|
|
615
|
+
});
|
|
616
|
+
});
|
|
@@ -20,10 +20,15 @@ import {
|
|
|
20
20
|
PreviewConfigBuilder,
|
|
21
21
|
} from "../../dsl/builder/screens_builder";
|
|
22
22
|
import { InvalidJobTypeScreenConfigException } from "../../../../core/exception/job_validation_exceptions";
|
|
23
|
+
import { InvalidFieldValueException } from "../../../../core/exception/invalid_field_value_exception";
|
|
23
24
|
import {
|
|
24
25
|
InvalidScreenOrderException,
|
|
25
26
|
MissingCaptureForPreviewException,
|
|
26
27
|
} from "../../../../core/exception/screen_config_validation_exceptions";
|
|
28
|
+
import type {
|
|
29
|
+
TokenConsent,
|
|
30
|
+
TokenPayload,
|
|
31
|
+
} from "../../../../data/model/token_payload";
|
|
27
32
|
|
|
28
33
|
function stubFaceAnalyzer(): FaceAnalyzer {
|
|
29
34
|
return {
|
|
@@ -733,6 +738,201 @@ describe("DocumentJobTypeValidator — Android-parity rules", () => {
|
|
|
733
738
|
});
|
|
734
739
|
});
|
|
735
740
|
|
|
741
|
+
describe("JobTypeValidator — token-provided user details & consent", () => {
|
|
742
|
+
function tokenPayload(overrides: Partial<TokenPayload> = {}): TokenPayload {
|
|
743
|
+
return {
|
|
744
|
+
hasGivenNames: false,
|
|
745
|
+
hasLastName: false,
|
|
746
|
+
hasEmail: false,
|
|
747
|
+
hasPhoneNumber: false,
|
|
748
|
+
...overrides,
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
const fullBinding = tokenPayload({
|
|
753
|
+
hasGivenNames: true,
|
|
754
|
+
hasLastName: true,
|
|
755
|
+
hasEmail: true,
|
|
756
|
+
hasPhoneNumber: true,
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
const completeTokenConsent: TokenConsent = {
|
|
760
|
+
granted: true,
|
|
761
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
762
|
+
noticeLanguage: "EN",
|
|
763
|
+
noticePrivacyPolicyUrl: "https://partner.test/privacy",
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
const validConsentInformation = {
|
|
767
|
+
granted: true,
|
|
768
|
+
grantedAt: "2024-01-01T00:00:00.000Z",
|
|
769
|
+
noticeLanguage: "EN",
|
|
770
|
+
noticePrivacyPolicyUrl: "https://example.com",
|
|
771
|
+
} as const;
|
|
772
|
+
|
|
773
|
+
// Steps + params that make each submitting job type otherwise valid,
|
|
774
|
+
// so the only variable under test is the userDetails / consent rule.
|
|
775
|
+
function validInputFor(jobType: JobType): JobTypeValidatorInput {
|
|
776
|
+
switch (jobType) {
|
|
777
|
+
case JobType.documentVerification:
|
|
778
|
+
case JobType.enhancedDocumentVerification:
|
|
779
|
+
return {
|
|
780
|
+
steps: [
|
|
781
|
+
consentStep(),
|
|
782
|
+
captureStep(CaptureType.selfie),
|
|
783
|
+
captureStep(CaptureType.document),
|
|
784
|
+
processingStep(),
|
|
785
|
+
],
|
|
786
|
+
network: validNetwork(jobType),
|
|
787
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
788
|
+
documentVerificationParams: validDocumentParams,
|
|
789
|
+
enhancedDocumentVerificationParams: validEnhancedDocumentParams,
|
|
790
|
+
};
|
|
791
|
+
case JobType.enhancedKyc:
|
|
792
|
+
return {
|
|
793
|
+
steps: [consentStep(), processingStep()],
|
|
794
|
+
network: validNetwork(jobType),
|
|
795
|
+
enhancedKYCParams: {
|
|
796
|
+
idType: "NATIONAL_ID",
|
|
797
|
+
idNumber: "1",
|
|
798
|
+
country: "KE",
|
|
799
|
+
},
|
|
800
|
+
};
|
|
801
|
+
default:
|
|
802
|
+
return {
|
|
803
|
+
steps: [
|
|
804
|
+
consentStep(),
|
|
805
|
+
captureStep(CaptureType.selfie),
|
|
806
|
+
processingStep(),
|
|
807
|
+
],
|
|
808
|
+
network: validNetwork(jobType),
|
|
809
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
810
|
+
biometricKYCParams: validBiometricParams,
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
const USER_DETAILS_JOB_TYPES: readonly JobType[] = [
|
|
816
|
+
JobType.smartSelfieEnrollment,
|
|
817
|
+
JobType.smartSelfieAuthentication,
|
|
818
|
+
JobType.biometricKyc,
|
|
819
|
+
JobType.documentVerification,
|
|
820
|
+
JobType.enhancedDocumentVerification,
|
|
821
|
+
JobType.enhancedKyc,
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
for (const jobType of USER_DETAILS_JOB_TYPES) {
|
|
825
|
+
it(`${jobType}: userDetails may be omitted when the token binds names + a contact field`, () => {
|
|
826
|
+
const issues = run({
|
|
827
|
+
...validInputFor(jobType),
|
|
828
|
+
tokenPayload: fullBinding,
|
|
829
|
+
});
|
|
830
|
+
expect(
|
|
831
|
+
issues.some(
|
|
832
|
+
(i) =>
|
|
833
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
834
|
+
i.message.includes("userDetails is required"),
|
|
835
|
+
),
|
|
836
|
+
).toBe(false);
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
it("a names-only token binding reports the missing contact field by name", () => {
|
|
841
|
+
const issues = run({
|
|
842
|
+
...validInputFor(JobType.smartSelfieEnrollment),
|
|
843
|
+
tokenPayload: tokenPayload({ hasGivenNames: true, hasLastName: true }),
|
|
844
|
+
});
|
|
845
|
+
expect(
|
|
846
|
+
issues.some(
|
|
847
|
+
(i) =>
|
|
848
|
+
i instanceof InvalidFieldValueException &&
|
|
849
|
+
i.message.includes("userDetails") &&
|
|
850
|
+
i.message.includes(
|
|
851
|
+
"at least one contact field must be bound by the token or supplied on the builder",
|
|
852
|
+
),
|
|
853
|
+
),
|
|
854
|
+
).toBe(true);
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
it("complete token consent satisfies the consent-source rule with no local source", () => {
|
|
858
|
+
const issues = run({
|
|
859
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
860
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
861
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
862
|
+
userDetails: validUserDetails,
|
|
863
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
864
|
+
});
|
|
865
|
+
expect(
|
|
866
|
+
issues.some(
|
|
867
|
+
(i) =>
|
|
868
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
869
|
+
i.message.includes(
|
|
870
|
+
"must include either a Consent screen or a pre-supplied consentInformation",
|
|
871
|
+
),
|
|
872
|
+
),
|
|
873
|
+
).toBe(false);
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
it("a partial token consent fails validation naming the missing subfields", () => {
|
|
877
|
+
const issues = run({
|
|
878
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
879
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
880
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
881
|
+
userDetails: validUserDetails,
|
|
882
|
+
tokenPayload: {
|
|
883
|
+
...tokenPayload(),
|
|
884
|
+
consent: {
|
|
885
|
+
granted: true,
|
|
886
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
887
|
+
},
|
|
888
|
+
},
|
|
889
|
+
});
|
|
890
|
+
expect(
|
|
891
|
+
issues.some(
|
|
892
|
+
(i) =>
|
|
893
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
894
|
+
i.message.includes(
|
|
895
|
+
"the token's consent binding is incomplete — missing: notice_language, notice_privacy_policy_url",
|
|
896
|
+
),
|
|
897
|
+
),
|
|
898
|
+
).toBe(true);
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
it("complete token consent alongside a consentInformation preset is allowed", () => {
|
|
902
|
+
const issues = run({
|
|
903
|
+
steps: [captureStep(CaptureType.selfie), processingStep()],
|
|
904
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
905
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
906
|
+
userDetails: validUserDetails,
|
|
907
|
+
consentInformation: validConsentInformation,
|
|
908
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
909
|
+
});
|
|
910
|
+
expect(
|
|
911
|
+
issues.some((i) => i instanceof InvalidJobTypeScreenConfigException),
|
|
912
|
+
).toBe(false);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it("the consent-source mutex still fires with complete token consent", () => {
|
|
916
|
+
const issues = run({
|
|
917
|
+
steps: [consentStep(), captureStep(CaptureType.selfie), processingStep()],
|
|
918
|
+
network: validNetwork(JobType.smartSelfieEnrollment),
|
|
919
|
+
ml: { analyzers: { selfie: stubFaceAnalyzer() } },
|
|
920
|
+
userDetails: validUserDetails,
|
|
921
|
+
consentInformation: validConsentInformation,
|
|
922
|
+
tokenPayload: { ...tokenPayload(), consent: completeTokenConsent },
|
|
923
|
+
});
|
|
924
|
+
expect(
|
|
925
|
+
issues.some(
|
|
926
|
+
(i) =>
|
|
927
|
+
i instanceof InvalidJobTypeScreenConfigException &&
|
|
928
|
+
i.message.includes(
|
|
929
|
+
"must not include both a Consent screen and a pre-supplied consentInformation",
|
|
930
|
+
),
|
|
931
|
+
),
|
|
932
|
+
).toBe(true);
|
|
933
|
+
});
|
|
934
|
+
});
|
|
935
|
+
|
|
736
936
|
describe("NonCaptureJobTypeValidator — Android-parity rules", () => {
|
|
737
937
|
it("passes a fully-configured EnhancedKyc flow", () => {
|
|
738
938
|
const issues = run({
|
|
@@ -9,8 +9,12 @@ import type {
|
|
|
9
9
|
EnhancedKYCParams,
|
|
10
10
|
UserDetails,
|
|
11
11
|
} from "../../../core/models/params";
|
|
12
|
-
import { JobType } from "../../../core/models/job_type";
|
|
13
|
-
import type {
|
|
12
|
+
import { JobType, jobTypeFromValue } from "../../../core/models/job_type";
|
|
13
|
+
import type {
|
|
14
|
+
DocumentCaptureConfigBuilder,
|
|
15
|
+
ScreenSpec,
|
|
16
|
+
SelfieCaptureConfigBuilder,
|
|
17
|
+
} from "../dsl/builder/screens_builder";
|
|
14
18
|
|
|
15
19
|
type ConsentScreenStep = Extract<ScreenSpec, { kind: "consent" }>;
|
|
16
20
|
import { IssueSeverity } from "../../../core/exception/issue_severity";
|
|
@@ -25,12 +29,15 @@ import {
|
|
|
25
29
|
MissingNetworkConfigurationBlockException,
|
|
26
30
|
} from "../../../core/exception/job_validation_exceptions";
|
|
27
31
|
import {
|
|
32
|
+
AgentModeWithEnhancedLivenessException,
|
|
28
33
|
InvalidDocumentCaptureConfigException,
|
|
29
34
|
InvalidMissingCaptureTypeException,
|
|
30
35
|
} from "../../../core/exception/capture_validation_exceptions";
|
|
31
36
|
import { EmptyScreensBlockException } from "../../../core/exception/screen_config_validation_exceptions";
|
|
37
|
+
import type { TokenPayload } from "../../../data/model/token_payload";
|
|
32
38
|
import { ValidationState, type ValidationIssue } from "./validation_state";
|
|
33
39
|
import { collectJobTypeIssues } from "./job_type_validator";
|
|
40
|
+
import { missingUserDetailsFieldIssues } from "./user_details_validator";
|
|
34
41
|
|
|
35
42
|
/**
|
|
36
43
|
* Pure flow validator — returns typed `UseSmileIDValidationException`
|
|
@@ -67,8 +74,59 @@ type FlowValidatorInput = {
|
|
|
67
74
|
| EnhancedDocumentVerificationParams
|
|
68
75
|
| undefined;
|
|
69
76
|
readonly userDetails?: UserDetails | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* User-detail/consent bindings decoded from the JWT `payload` claim.
|
|
79
|
+
* `undefined` (no token, offline mode, legacy token) keeps every rule
|
|
80
|
+
* exactly as strict as before token support existed.
|
|
81
|
+
*/
|
|
82
|
+
readonly tokenPayload?: TokenPayload | undefined;
|
|
70
83
|
};
|
|
71
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Agent mode captures on the rear lens while the head-turn challenge is only
|
|
87
|
+
* validated for front-camera framing, so the pair is refused rather than
|
|
88
|
+
* silently dropping one of them.
|
|
89
|
+
*/
|
|
90
|
+
function selfieConfigIssues(
|
|
91
|
+
config: SelfieCaptureConfigBuilder | undefined,
|
|
92
|
+
): ValidationIssue[] {
|
|
93
|
+
if (config?.allowAgentMode && config.enableEnhancedLiveness) {
|
|
94
|
+
return [new AgentModeWithEnhancedLivenessException()];
|
|
95
|
+
}
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Surfaces at most one issue: missing block > missing type > bad aspect ratio. */
|
|
100
|
+
function documentConfigIssues(
|
|
101
|
+
config: DocumentCaptureConfigBuilder | undefined,
|
|
102
|
+
): ValidationIssue[] {
|
|
103
|
+
if (config === undefined) {
|
|
104
|
+
return [
|
|
105
|
+
new InvalidDocumentCaptureConfigException({
|
|
106
|
+
reason: "document is required when mode is Document",
|
|
107
|
+
}),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
if (config.documentType === undefined) {
|
|
111
|
+
return [
|
|
112
|
+
new InvalidDocumentCaptureConfigException({
|
|
113
|
+
reason: "documentType is required for document capture",
|
|
114
|
+
}),
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
if (
|
|
118
|
+
config.knownIdAspectRatio !== undefined &&
|
|
119
|
+
config.knownIdAspectRatio <= 0
|
|
120
|
+
) {
|
|
121
|
+
return [
|
|
122
|
+
new InvalidDocumentCaptureConfigException({
|
|
123
|
+
reason: "knownIdAspectRatio must be > 0 if provided",
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
72
130
|
/**
|
|
73
131
|
* Quick triage of the in-progress builder state. Today the only rule
|
|
74
132
|
* surfaced is the empty-screens-block error — `b.screens(...)` must
|
|
@@ -113,32 +171,11 @@ export function runFlowValidation(
|
|
|
113
171
|
if (step.config.captureType === undefined) {
|
|
114
172
|
issues.push(new InvalidMissingCaptureTypeException());
|
|
115
173
|
}
|
|
116
|
-
|
|
117
|
-
|
|
174
|
+
if (step.config.captureType === CaptureType.selfie) {
|
|
175
|
+
issues.push(...selfieConfigIssues(step.config.selfieConfig));
|
|
176
|
+
}
|
|
118
177
|
if (step.config.captureType === CaptureType.document) {
|
|
119
|
-
|
|
120
|
-
if (documentConfig === undefined) {
|
|
121
|
-
issues.push(
|
|
122
|
-
new InvalidDocumentCaptureConfigException({
|
|
123
|
-
reason: "document is required when mode is Document",
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
} else if (documentConfig.documentType === undefined) {
|
|
127
|
-
issues.push(
|
|
128
|
-
new InvalidDocumentCaptureConfigException({
|
|
129
|
-
reason: "documentType is required for document capture",
|
|
130
|
-
}),
|
|
131
|
-
);
|
|
132
|
-
} else if (
|
|
133
|
-
documentConfig.knownIdAspectRatio !== undefined &&
|
|
134
|
-
documentConfig.knownIdAspectRatio <= 0
|
|
135
|
-
) {
|
|
136
|
-
issues.push(
|
|
137
|
-
new InvalidDocumentCaptureConfigException({
|
|
138
|
-
reason: "knownIdAspectRatio must be > 0 if provided",
|
|
139
|
-
}),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
178
|
+
issues.push(...documentConfigIssues(step.config.documentConfig));
|
|
142
179
|
}
|
|
143
180
|
}
|
|
144
181
|
}
|
|
@@ -156,9 +193,13 @@ export function runFlowValidation(
|
|
|
156
193
|
// build (ERROR) whenever it's missing — whether or not a network
|
|
157
194
|
// block was provided. The missing-network WARNING above still fires
|
|
158
195
|
// alongside this when the block is absent.
|
|
196
|
+
// `jobTypeFromValue` collapses the unknown sentinel and any out-of-enum value, so an unrecognised job_type_id fails the build too.
|
|
159
197
|
if (hasProcessingScreen) {
|
|
160
198
|
const jobType = input.network?.config?.jobType;
|
|
161
|
-
if (
|
|
199
|
+
if (
|
|
200
|
+
jobType === undefined ||
|
|
201
|
+
jobTypeFromValue(jobType) === JobType.unknown
|
|
202
|
+
) {
|
|
162
203
|
issues.push(new MissingJobTypeException());
|
|
163
204
|
}
|
|
164
205
|
}
|
|
@@ -209,7 +250,9 @@ export function runFlowValidation(
|
|
|
209
250
|
);
|
|
210
251
|
}
|
|
211
252
|
|
|
212
|
-
|
|
253
|
+
// A token consent binding supersedes the preset entirely (token data
|
|
254
|
+
// always wins), so the ignored preset's contents are not validated.
|
|
255
|
+
if (input.consentInformation && input.tokenPayload?.consent === undefined) {
|
|
213
256
|
issues.push(...runConsentValidation(input.consentInformation));
|
|
214
257
|
}
|
|
215
258
|
if (input.enhancedKYCParams) {
|
|
@@ -233,7 +276,9 @@ export function runFlowValidation(
|
|
|
233
276
|
);
|
|
234
277
|
}
|
|
235
278
|
if (input.userDetails) {
|
|
236
|
-
issues.push(
|
|
279
|
+
issues.push(
|
|
280
|
+
...runUserDetailsValidation(input.userDetails, input.tokenPayload),
|
|
281
|
+
);
|
|
237
282
|
}
|
|
238
283
|
|
|
239
284
|
// Every flow-composition rule (screen ordering, duplicate screens,
|
|
@@ -379,40 +424,23 @@ function runKYCParamsValidation(
|
|
|
379
424
|
* keep working unchanged after the consolidation away from nesting
|
|
380
425
|
* `userDetails` inside the KYC param shapes.
|
|
381
426
|
*
|
|
427
|
+
* Each field requirement is evaluated against the union of builder
|
|
428
|
+
* input and token bindings: a field the token binds (see
|
|
429
|
+
* `TokenPayload`) never has to be supplied locally — the server
|
|
430
|
+
* injects it, and the SDK drops the local value from the wire. With
|
|
431
|
+
* no `tokenPayload` the behavior is exactly the pre-token rule set.
|
|
432
|
+
*
|
|
382
433
|
* Exposed via `b.validateUserDetails(payload)` so partners can
|
|
383
434
|
* pre-flight a payload before assigning to `b.userDetails`, mirroring
|
|
384
435
|
* the existing `b.validateConsent` / `b.validateEnhancedKYCParams`
|
|
385
|
-
* pattern
|
|
436
|
+
* pattern (the pre-flight helper always validates strictly, without
|
|
437
|
+
* token context).
|
|
386
438
|
*/
|
|
387
439
|
export function runUserDetailsValidation(
|
|
388
440
|
userDetails: UserDetails,
|
|
441
|
+
tokenPayload?: TokenPayload,
|
|
389
442
|
): ValidationIssue[] {
|
|
390
|
-
|
|
391
|
-
for (const field of ["givenNames", "lastName"] as const) {
|
|
392
|
-
const value = userDetails[field];
|
|
393
|
-
if (!value || !value.trim()) {
|
|
394
|
-
issues.push(
|
|
395
|
-
new InvalidFieldValueException({
|
|
396
|
-
fieldName: `userDetails.${field}`,
|
|
397
|
-
reason: "must not be blank",
|
|
398
|
-
}),
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
// At-least-one-of contact rule: email and phoneNumber are each
|
|
403
|
-
// optional, but the partner must supply one of them.
|
|
404
|
-
const hasEmail = !!userDetails.email && userDetails.email.trim().length > 0;
|
|
405
|
-
const hasPhone =
|
|
406
|
-
!!userDetails.phoneNumber && userDetails.phoneNumber.trim().length > 0;
|
|
407
|
-
if (!hasEmail && !hasPhone) {
|
|
408
|
-
issues.push(
|
|
409
|
-
new InvalidFieldValueException({
|
|
410
|
-
fieldName: "userDetails",
|
|
411
|
-
reason: "at least one of email or phoneNumber must be non blank",
|
|
412
|
-
}),
|
|
413
|
-
);
|
|
414
|
-
}
|
|
415
|
-
return issues;
|
|
443
|
+
return missingUserDetailsFieldIssues(userDetails, tokenPayload);
|
|
416
444
|
}
|
|
417
445
|
|
|
418
446
|
/**
|