@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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { create as createRenderer, act } from "react-test-renderer";
|
|
4
|
+
|
|
5
|
+
// `vi.mock` is hoisted above the imports, so anything its factory closes over
|
|
6
|
+
// must be created with `vi.hoisted` or it is still in the TDZ when the factory
|
|
7
|
+
// runs.
|
|
8
|
+
const { screenProps } = vi.hoisted(() => ({
|
|
9
|
+
screenProps: { current: {} as Record<string, unknown> },
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Stand in for the leaf screen so these tests exercise the orchestration
|
|
13
|
+
// (submission kick-off, state mapping, exit routing) rather than re-testing
|
|
14
|
+
// the processing UI, which has its own component suite.
|
|
15
|
+
vi.mock("../../../screen/use_smile_id_processing_screen", () => ({
|
|
16
|
+
UseSmileIDProcessingScreen: (props: Record<string, unknown>) => {
|
|
17
|
+
screenProps.current = props;
|
|
18
|
+
return null;
|
|
19
|
+
},
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
import { OrchestratedProcessingScreen } from "../orchestrated_processing_screen";
|
|
23
|
+
import { FlowNavigationProvider } from "../../flow_navigation_context";
|
|
24
|
+
import {
|
|
25
|
+
NetworkServerException,
|
|
26
|
+
NetworkTransportException,
|
|
27
|
+
NetworkUnknownException,
|
|
28
|
+
ProcessingCancelledException,
|
|
29
|
+
} from "../../../../core/exception";
|
|
30
|
+
import { JobType } from "../../../../core/models/job_type";
|
|
31
|
+
import { ProcessingConfigBuilder } from "../../dsl/builder/screens_builder";
|
|
32
|
+
import type { FlowNavigationManager } from "../../flow_navigation_manager";
|
|
33
|
+
import type { JobSubmissionState } from "../../job_submission_state";
|
|
34
|
+
|
|
35
|
+
type Fn = ReturnType<typeof vi.fn>;
|
|
36
|
+
|
|
37
|
+
type FakeManager = FlowNavigationManager & {
|
|
38
|
+
beginSubmissionIfNeeded: Fn;
|
|
39
|
+
retrySubmission: Fn;
|
|
40
|
+
navigateToNext: Fn;
|
|
41
|
+
exit: Fn;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type ManagerOverrides = {
|
|
45
|
+
readonly jobType?: number;
|
|
46
|
+
readonly submissionState?: JobSubmissionState;
|
|
47
|
+
readonly frontDocumentPath?: string;
|
|
48
|
+
readonly selfiePath?: string;
|
|
49
|
+
readonly documentFrameAspectRatio?: number;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function makeManager(overrides: ManagerOverrides = {}): FakeManager {
|
|
53
|
+
return {
|
|
54
|
+
beginSubmissionIfNeeded: vi.fn(),
|
|
55
|
+
retrySubmission: vi.fn(),
|
|
56
|
+
navigateToNext: vi.fn(),
|
|
57
|
+
navigateBack: vi.fn(),
|
|
58
|
+
exit: vi.fn(),
|
|
59
|
+
jobSubmissionState: overrides.submissionState ?? { kind: "notAttempted" },
|
|
60
|
+
documentFrameAspectRatio: overrides.documentFrameAspectRatio,
|
|
61
|
+
captured: {
|
|
62
|
+
...(overrides.frontDocumentPath !== undefined
|
|
63
|
+
? { frontDocumentPath: overrides.frontDocumentPath }
|
|
64
|
+
: {}),
|
|
65
|
+
...(overrides.selfiePath !== undefined
|
|
66
|
+
? { selfiePath: overrides.selfiePath }
|
|
67
|
+
: {}),
|
|
68
|
+
},
|
|
69
|
+
configuration: {
|
|
70
|
+
network:
|
|
71
|
+
overrides.jobType !== undefined
|
|
72
|
+
? { config: { jobType: overrides.jobType } }
|
|
73
|
+
: undefined,
|
|
74
|
+
},
|
|
75
|
+
} as unknown as FakeManager;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function makeConfiguration(
|
|
79
|
+
overrides: Partial<ProcessingConfigBuilder> = {},
|
|
80
|
+
): ProcessingConfigBuilder {
|
|
81
|
+
return Object.assign(new ProcessingConfigBuilder(), overrides);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderWith(
|
|
85
|
+
manager: FlowNavigationManager,
|
|
86
|
+
configuration: ProcessingConfigBuilder = makeConfiguration(),
|
|
87
|
+
) {
|
|
88
|
+
act(() => {
|
|
89
|
+
createRenderer(
|
|
90
|
+
<FlowNavigationProvider manager={manager}>
|
|
91
|
+
<OrchestratedProcessingScreen configuration={configuration} />
|
|
92
|
+
</FlowNavigationProvider>,
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
screenProps.current = {};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe("OrchestratedProcessingScreen — submission lifecycle", () => {
|
|
102
|
+
it("kicks off submission exactly once on mount", () => {
|
|
103
|
+
const manager = makeManager();
|
|
104
|
+
renderWith(manager);
|
|
105
|
+
|
|
106
|
+
expect(manager.beginSubmissionIfNeeded).toHaveBeenCalledTimes(1);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("retry re-fires submission rather than navigating", () => {
|
|
110
|
+
const manager = makeManager({
|
|
111
|
+
submissionState: { kind: "failure", error: new Error("network down") },
|
|
112
|
+
});
|
|
113
|
+
renderWith(manager);
|
|
114
|
+
|
|
115
|
+
(screenProps.current.onRetry as () => void)();
|
|
116
|
+
|
|
117
|
+
expect(manager.retrySubmission).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(manager.navigateToNext).not.toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("continue advances the flow", () => {
|
|
122
|
+
const manager = makeManager();
|
|
123
|
+
renderWith(manager);
|
|
124
|
+
|
|
125
|
+
(screenProps.current.onContinue as () => void)();
|
|
126
|
+
|
|
127
|
+
expect(manager.navigateToNext).toHaveBeenCalledTimes(1);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("exit ends the flow with a typed ProcessingCancelledException", () => {
|
|
131
|
+
const manager = makeManager();
|
|
132
|
+
renderWith(manager);
|
|
133
|
+
|
|
134
|
+
(screenProps.current.onExit as () => void)();
|
|
135
|
+
|
|
136
|
+
const [[reason]] = manager.exit.mock.calls as [
|
|
137
|
+
[{ kind: string; error: unknown }],
|
|
138
|
+
];
|
|
139
|
+
expect(reason.kind).toBe("failed");
|
|
140
|
+
expect(reason.error).toBeInstanceOf(ProcessingCancelledException);
|
|
141
|
+
expect(manager.navigateToNext).not.toHaveBeenCalled();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("exit after a failed submission delivers that failure, not a cancellation", () => {
|
|
145
|
+
const submissionError = new Error("401 — invalid API key");
|
|
146
|
+
const manager = makeManager({
|
|
147
|
+
submissionState: { kind: "failure", error: submissionError },
|
|
148
|
+
});
|
|
149
|
+
renderWith(manager);
|
|
150
|
+
|
|
151
|
+
(screenProps.current.onExit as () => void)();
|
|
152
|
+
|
|
153
|
+
const [[reason]] = manager.exit.mock.calls as [
|
|
154
|
+
[{ kind: string; error: unknown }],
|
|
155
|
+
];
|
|
156
|
+
expect(reason.kind).toBe("failed");
|
|
157
|
+
expect(reason.error).toBe(submissionError);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("exit while the job is still in flight is a cancellation", () => {
|
|
161
|
+
const manager = makeManager({ submissionState: { kind: "loading" } });
|
|
162
|
+
renderWith(manager);
|
|
163
|
+
|
|
164
|
+
(screenProps.current.onExit as () => void)();
|
|
165
|
+
|
|
166
|
+
const [[reason]] = manager.exit.mock.calls as [
|
|
167
|
+
[{ kind: string; error: unknown }],
|
|
168
|
+
];
|
|
169
|
+
expect(reason.error).toBeInstanceOf(ProcessingCancelledException);
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
describe("OrchestratedProcessingScreen — submission state mapping", () => {
|
|
174
|
+
it("shows loading during the pre-submission window so the card is never empty", () => {
|
|
175
|
+
renderWith(makeManager({ submissionState: { kind: "notAttempted" } }));
|
|
176
|
+
expect(screenProps.current.state).toEqual({ kind: "loading" });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("shows loading while the job is in flight", () => {
|
|
180
|
+
renderWith(makeManager({ submissionState: { kind: "loading" } }));
|
|
181
|
+
expect(screenProps.current.state).toEqual({ kind: "loading" });
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("surfaces the server's message on success", () => {
|
|
185
|
+
renderWith(
|
|
186
|
+
makeManager({
|
|
187
|
+
submissionState: {
|
|
188
|
+
kind: "success",
|
|
189
|
+
response: {
|
|
190
|
+
userId: "user-1",
|
|
191
|
+
jobId: "job-1",
|
|
192
|
+
status: "approved",
|
|
193
|
+
message: "Job approved",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(screenProps.current.state).toEqual({
|
|
200
|
+
kind: "success",
|
|
201
|
+
message: "Job approved",
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("surfaces the server's own text on a server failure", () => {
|
|
206
|
+
renderWith(
|
|
207
|
+
makeManager({
|
|
208
|
+
submissionState: {
|
|
209
|
+
kind: "failure",
|
|
210
|
+
error: new NetworkServerException({
|
|
211
|
+
statusCode: 401,
|
|
212
|
+
message: "Invalid token",
|
|
213
|
+
}),
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(screenProps.current.state).toEqual({
|
|
219
|
+
kind: "failed",
|
|
220
|
+
errorMessage: "Invalid token",
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("shows the no-connection copy on a transport failure", () => {
|
|
225
|
+
renderWith(
|
|
226
|
+
makeManager({
|
|
227
|
+
submissionState: {
|
|
228
|
+
kind: "failure",
|
|
229
|
+
error: new NetworkTransportException({ cause: new Error("ECONNREFUSED") }),
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
expect(screenProps.current.state).toEqual({
|
|
235
|
+
kind: "failed",
|
|
236
|
+
errorMessage: "No internet connection.",
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("shows the generic copy for unknown network failures", () => {
|
|
241
|
+
renderWith(
|
|
242
|
+
makeManager({
|
|
243
|
+
submissionState: {
|
|
244
|
+
kind: "failure",
|
|
245
|
+
error: new NetworkUnknownException({
|
|
246
|
+
cause: new Error("ENOENT /data/user/0/…/selfie.jpg"),
|
|
247
|
+
}),
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
expect(screenProps.current.state).toEqual({
|
|
253
|
+
kind: "failed",
|
|
254
|
+
errorMessage: "Something went wrong. Try again.",
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("surfaces the error message on other failures", () => {
|
|
259
|
+
renderWith(
|
|
260
|
+
makeManager({
|
|
261
|
+
submissionState: { kind: "failure", error: new Error("upload failed") },
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
expect(screenProps.current.state).toEqual({
|
|
266
|
+
kind: "failed",
|
|
267
|
+
errorMessage: "upload failed",
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("OrchestratedProcessingScreen — artifact rendering", () => {
|
|
273
|
+
it("renders the captured front document for a document job", () => {
|
|
274
|
+
renderWith(
|
|
275
|
+
makeManager({
|
|
276
|
+
jobType: JobType.documentVerification,
|
|
277
|
+
frontDocumentPath: "file:///front.jpg",
|
|
278
|
+
documentFrameAspectRatio: 1.586,
|
|
279
|
+
}),
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
expect(screenProps.current.documentImageUri).toBe("file:///front.jpg");
|
|
283
|
+
expect(screenProps.current.documentAspectRatio).toBeCloseTo(1.586, 5);
|
|
284
|
+
expect(screenProps.current.showFaceOval).toBe(true);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("omits the document aspect ratio when the manager has none", () => {
|
|
288
|
+
renderWith(
|
|
289
|
+
makeManager({
|
|
290
|
+
jobType: JobType.documentVerification,
|
|
291
|
+
frontDocumentPath: "file:///front.jpg",
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
expect(screenProps.current.documentImageUri).toBe("file:///front.jpg");
|
|
296
|
+
expect("documentAspectRatio" in screenProps.current).toBe(false);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("ignores a captured document on a selfie-only job", () => {
|
|
300
|
+
renderWith(
|
|
301
|
+
makeManager({
|
|
302
|
+
jobType: JobType.smartSelfieEnrollment,
|
|
303
|
+
frontDocumentPath: "file:///front.jpg",
|
|
304
|
+
selfiePath: "file:///selfie.jpg",
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
expect("documentImageUri" in screenProps.current).toBe(false);
|
|
309
|
+
expect(screenProps.current.capturedSelfiePath).toBe("file:///selfie.jpg");
|
|
310
|
+
expect(screenProps.current.showFaceOval).toBe(true);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it("hides the face oval for a job that captures neither", () => {
|
|
314
|
+
renderWith(makeManager({ jobType: JobType.enhancedKyc }));
|
|
315
|
+
|
|
316
|
+
expect(screenProps.current.showFaceOval).toBe(false);
|
|
317
|
+
expect("capturedSelfiePath" in screenProps.current).toBe(false);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("treats a flow with no network config as the unknown job type", () => {
|
|
321
|
+
renderWith(makeManager());
|
|
322
|
+
|
|
323
|
+
expect(screenProps.current.showFaceOval).toBe(false);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("forwards the partner's progress and attribution switches", () => {
|
|
327
|
+
renderWith(
|
|
328
|
+
makeManager(),
|
|
329
|
+
makeConfiguration({
|
|
330
|
+
showProgressPercentage: false,
|
|
331
|
+
showAttribution: false,
|
|
332
|
+
}),
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
expect(screenProps.current.showProgressPercentage).toBe(false);
|
|
336
|
+
expect(screenProps.current.showAttribution).toBe(false);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
@@ -75,10 +75,15 @@ export function OrchestratedCaptureScreen({
|
|
|
75
75
|
// selfie to portrait. The flip interstitial is a portrait instruction screen,
|
|
76
76
|
// so it must NOT inherit the document's landscape lock — force portrait while
|
|
77
77
|
// it shows, then revert to the document orientation for the back capture.
|
|
78
|
+
const exitOrientations =
|
|
79
|
+
navigationManager.configuration.config?.exitOrientations;
|
|
78
80
|
useCaptureOrientationLock(
|
|
79
81
|
captureType === CaptureType.document && !showBackInstructions
|
|
80
82
|
? (documentType?.orientation ?? "Landscape")
|
|
81
83
|
: "Portrait",
|
|
84
|
+
{
|
|
85
|
+
...(exitOrientations !== undefined ? { exitLock: exitOrientations } : {}),
|
|
86
|
+
},
|
|
82
87
|
);
|
|
83
88
|
|
|
84
89
|
// Selfie also pushes the registry-built wire FaceConfig; documents have no
|
|
@@ -184,6 +189,7 @@ export function OrchestratedCaptureScreen({
|
|
|
184
189
|
enableEnhancedLiveness={
|
|
185
190
|
configuration.selfieConfig?.enableEnhancedLiveness ?? false
|
|
186
191
|
}
|
|
192
|
+
allowAgentMode={configuration.selfieConfig?.allowAgentMode ?? false}
|
|
187
193
|
{...(captureType === CaptureType.document && documentConfig
|
|
188
194
|
? {
|
|
189
195
|
// Type ratio → partner override → orientation default; matches
|
|
@@ -261,9 +267,18 @@ export function OrchestratedCaptureScreen({
|
|
|
261
267
|
...(fingerprint ? { fingerprint } : {}),
|
|
262
268
|
}),
|
|
263
269
|
)
|
|
264
|
-
.then(() =>
|
|
265
|
-
SmileExifTagger.finalize(primary, imageTypeForCapture)
|
|
266
|
-
|
|
270
|
+
.then(async () => {
|
|
271
|
+
await SmileExifTagger.finalize(primary, imageTypeForCapture);
|
|
272
|
+
// Liveness frames ship in the same submission, so they need the tag too.
|
|
273
|
+
await Promise.all(
|
|
274
|
+
livenessUrls.map((livenessUrl) =>
|
|
275
|
+
SmileExifTagger.finalize(livenessUrl, ImageType.Liveness),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
})
|
|
279
|
+
// Tagging is fail-open: a rejection must not become an unhandled
|
|
280
|
+
// rejection, and navigation still runs in `finally`.
|
|
281
|
+
.catch(() => undefined)
|
|
267
282
|
.finally(async () => {
|
|
268
283
|
if (captureType === CaptureType.selfie) {
|
|
269
284
|
navigationManager.setCapturedSelfie(primary, livenessUrls);
|
|
@@ -2,6 +2,7 @@ import type { ConsentConfigBuilder } from "../dsl/builder/screens_builder";
|
|
|
2
2
|
import type { ConsentInformation } from "../../../core/models/params";
|
|
3
3
|
import { UseSmileIDCrashReporting } from "@smileid/usesmileid_bridge";
|
|
4
4
|
import { ConsentDeniedException } from "../../../core/exception";
|
|
5
|
+
import { GlobalPrivacyControl } from "../../../core/models/global_privacy_control";
|
|
5
6
|
import { UseSmileIDConsentScreen } from "../../screen/use_smile_id_consent_screen";
|
|
6
7
|
import { UseSmileIDOrchestratedScreenHost } from "./use_smile_id_orchestrated_screen_host";
|
|
7
8
|
|
|
@@ -25,6 +26,10 @@ export function OrchestratedConsentScreen({
|
|
|
25
26
|
content={({ navigationManager, onNavigateNext }) => (
|
|
26
27
|
<UseSmileIDConsentScreen
|
|
27
28
|
partnerName={configuration.partnerName}
|
|
29
|
+
globalPrivacyControl={
|
|
30
|
+
navigationManager.configuration.config?.globalPrivacyControl ??
|
|
31
|
+
GlobalPrivacyControl.OptOut
|
|
32
|
+
}
|
|
28
33
|
{...(configuration.partnerIcon !== undefined
|
|
29
34
|
? { partnerIcon: configuration.partnerIcon }
|
|
30
35
|
: {})}
|
|
@@ -50,18 +50,6 @@ export function OrchestratedPreviewScreen({
|
|
|
50
50
|
{...(navigationManager.captured.selfiePath !== undefined
|
|
51
51
|
? { capturedSelfiePath: navigationManager.captured.selfiePath }
|
|
52
52
|
: {})}
|
|
53
|
-
{...(configuration.continueButton
|
|
54
|
-
? {
|
|
55
|
-
continueButton: ({ onClick }: { onClick: () => void }) =>
|
|
56
|
-
configuration.continueButton?.({ onClick, enabled: true }),
|
|
57
|
-
}
|
|
58
|
-
: {})}
|
|
59
|
-
{...(configuration.cancelButton
|
|
60
|
-
? {
|
|
61
|
-
cancelButton: ({ onClick }: { onClick: () => void }) =>
|
|
62
|
-
configuration.cancelButton?.({ onClick, enabled: true }),
|
|
63
|
-
}
|
|
64
|
-
: {})}
|
|
65
53
|
onAccept={onNavigateNext}
|
|
66
54
|
onReject={() => {
|
|
67
55
|
if (configuration.allowRetake) onBack();
|
|
@@ -7,7 +7,15 @@ import {
|
|
|
7
7
|
type ProcessingState,
|
|
8
8
|
} from "../../screen/use_smile_id_processing_screen";
|
|
9
9
|
import { UseSmileIDOrchestratedScreenHost } from "./use_smile_id_orchestrated_screen_host";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
NetworkResponseParseException,
|
|
12
|
+
NetworkTimeoutException,
|
|
13
|
+
NetworkTransportException,
|
|
14
|
+
NetworkUnknownException,
|
|
15
|
+
ProcessingCancelledException,
|
|
16
|
+
} from "../../../core/exception";
|
|
17
|
+
import { useUseSmileIDLocalizations } from "../../../l10n/use_smile_id_localizations_provider";
|
|
18
|
+
import type { UseSmileIDLocalizations } from "../../../l10n/use_smile_id_localizations";
|
|
11
19
|
import {
|
|
12
20
|
JobType,
|
|
13
21
|
requiresDocument,
|
|
@@ -68,9 +76,11 @@ function ProcessingScreenBody(props: ProcessingScreenBodyProps) {
|
|
|
68
76
|
: undefined;
|
|
69
77
|
const documentAspectRatio = navigationManager.documentFrameAspectRatio;
|
|
70
78
|
|
|
79
|
+
const loc = useUseSmileIDLocalizations();
|
|
80
|
+
|
|
71
81
|
return (
|
|
72
82
|
<UseSmileIDProcessingScreen
|
|
73
|
-
state={mapToProcessingState(navigationManager.jobSubmissionState)}
|
|
83
|
+
state={mapToProcessingState(navigationManager.jobSubmissionState, loc)}
|
|
74
84
|
showFaceOval={requiresSelfie(jobType) || requiresDocument(jobType)}
|
|
75
85
|
showProgressPercentage={props.processingConfig.showProgressPercentage}
|
|
76
86
|
showAttribution={props.processingConfig.showAttribution}
|
|
@@ -86,19 +96,29 @@ function ProcessingScreenBody(props: ProcessingScreenBodyProps) {
|
|
|
86
96
|
onExit={() =>
|
|
87
97
|
navigationManager.exit({
|
|
88
98
|
kind: "failed",
|
|
89
|
-
error:
|
|
99
|
+
error: exitError(navigationManager.jobSubmissionState),
|
|
90
100
|
})
|
|
91
101
|
}
|
|
92
102
|
/>
|
|
93
103
|
);
|
|
94
104
|
}
|
|
95
105
|
|
|
106
|
+
/** The error the flow exits with when the user leaves the processing screen; an already-failed submission is delivered as its own failure. */
|
|
107
|
+
function exitError(state: JobSubmissionState): Error {
|
|
108
|
+
return state.kind === "failure"
|
|
109
|
+
? state.error
|
|
110
|
+
: new ProcessingCancelledException();
|
|
111
|
+
}
|
|
112
|
+
|
|
96
113
|
/**
|
|
97
114
|
* Translate `JobSubmissionState` to the screen's `ProcessingState`.
|
|
98
115
|
* `notAttempted` maps to `loading` so the brief pre-submission window
|
|
99
116
|
* doesn't render an empty card.
|
|
100
117
|
*/
|
|
101
|
-
function mapToProcessingState(
|
|
118
|
+
function mapToProcessingState(
|
|
119
|
+
state: JobSubmissionState,
|
|
120
|
+
loc: UseSmileIDLocalizations,
|
|
121
|
+
): ProcessingState {
|
|
102
122
|
switch (state.kind) {
|
|
103
123
|
case "notAttempted":
|
|
104
124
|
case "loading":
|
|
@@ -106,6 +126,23 @@ function mapToProcessingState(state: JobSubmissionState): ProcessingState {
|
|
|
106
126
|
case "success":
|
|
107
127
|
return { kind: "success", message: state.response.message };
|
|
108
128
|
case "failure":
|
|
109
|
-
return { kind: "failed", errorMessage: state.error
|
|
129
|
+
return { kind: "failed", errorMessage: userFacingMessage(state.error, loc) };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Transport/timeout and parse/unknown network failures carry technical text — every other failure keeps its message. */
|
|
134
|
+
function userFacingMessage(error: Error, loc: UseSmileIDLocalizations): string {
|
|
135
|
+
if (
|
|
136
|
+
error instanceof NetworkTransportException ||
|
|
137
|
+
error instanceof NetworkTimeoutException
|
|
138
|
+
) {
|
|
139
|
+
return loc.siProcessingErrorNoConnection;
|
|
140
|
+
}
|
|
141
|
+
if (
|
|
142
|
+
error instanceof NetworkResponseParseException ||
|
|
143
|
+
error instanceof NetworkUnknownException
|
|
144
|
+
) {
|
|
145
|
+
return loc.siProcessingErrorGeneric;
|
|
110
146
|
}
|
|
147
|
+
return error.message;
|
|
111
148
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useMemo, useReducer, useRef } from "react";
|
|
2
|
-
import { Text, useColorScheme, View } from "react-native";
|
|
1
|
+
import { useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
2
|
+
import { AppState, Text, useColorScheme, View } from "react-native";
|
|
3
3
|
import type { FlowConfiguration } from "./config/flow_configuration";
|
|
4
4
|
import type { FlowBuildResult } from "./dsl/flow_build_result";
|
|
5
5
|
import {
|
|
@@ -39,8 +39,8 @@ import {
|
|
|
39
39
|
* whether the partner constructs `network.retry` explicitly or not.
|
|
40
40
|
*/
|
|
41
41
|
const DEFAULT_RETRY: RetryConfig = Object.freeze({
|
|
42
|
-
maxAttempts:
|
|
43
|
-
strategy: RetryStrategy.
|
|
42
|
+
maxAttempts: 1,
|
|
43
|
+
strategy: RetryStrategy.none,
|
|
44
44
|
initialDelay: 1,
|
|
45
45
|
multiplier: 2,
|
|
46
46
|
maxDelay: 32,
|
|
@@ -102,6 +102,9 @@ export type FlowSuccessRendererProps = {
|
|
|
102
102
|
readonly configuration: FlowConfiguration;
|
|
103
103
|
};
|
|
104
104
|
|
|
105
|
+
/** Ceiling on the first-frame font wait — past it the flow renders regardless. */
|
|
106
|
+
const FONT_REGISTRATION_TIMEOUT_MS = 1000;
|
|
107
|
+
|
|
105
108
|
/**
|
|
106
109
|
* Walks the partner through the configured screen list, dispatches
|
|
107
110
|
* frames to the chosen analyzer, submits the resulting artifacts, and
|
|
@@ -141,12 +144,23 @@ export function FlowSuccessRenderer({
|
|
|
141
144
|
? defaultDarkTheme
|
|
142
145
|
: defaultLightTheme;
|
|
143
146
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
// The first frame waits on font registration: a label laid out in the system font and re-measured in DM Sans can settle clipped.
|
|
148
|
+
const fontsLoaded = useUseSmileIDFonts();
|
|
149
|
+
const [fontWaitElapsed, setFontWaitElapsed] = useState(false);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (fontsLoaded) return;
|
|
152
|
+
const timer = setTimeout(
|
|
153
|
+
() => setFontWaitElapsed(true),
|
|
154
|
+
FONT_REGISTRATION_TIMEOUT_MS,
|
|
155
|
+
);
|
|
156
|
+
return () => clearTimeout(timer);
|
|
157
|
+
}, [fontsLoaded]);
|
|
148
158
|
const enableCrashReporting =
|
|
149
159
|
configuration.config?.enableCrashReporting !== false;
|
|
160
|
+
// Sandbox sessions are test traffic and must not pollute the production
|
|
161
|
+
// Sentry project; the reporter also drops debug bundles internally.
|
|
162
|
+
const useSandbox =
|
|
163
|
+
configuration.network?.config?.partnerConfig?.useSandbox === true;
|
|
150
164
|
|
|
151
165
|
// Per-flow session id used as the Sentry user-id so every event
|
|
152
166
|
// captured during this verification flow correlates under one issue
|
|
@@ -160,7 +174,10 @@ export function FlowSuccessRenderer({
|
|
|
160
174
|
|
|
161
175
|
useEffect(() => {
|
|
162
176
|
if (enableCrashReporting) {
|
|
163
|
-
|
|
177
|
+
// Tear down any prior client first so a changed useSandbox flag takes
|
|
178
|
+
// effect — enable() short-circuits when already enabled.
|
|
179
|
+
UseSmileIDCrashReporting.disable();
|
|
180
|
+
UseSmileIDCrashReporting.enable({ useSandbox });
|
|
164
181
|
UseSmileIDCrashReporting.setUserId(flowSessionIdRef.current!);
|
|
165
182
|
UseSmileIDCrashReporting.captureBreadcrumb({
|
|
166
183
|
category: "flow.navigation",
|
|
@@ -170,7 +187,7 @@ export function FlowSuccessRenderer({
|
|
|
170
187
|
return () => {
|
|
171
188
|
UseSmileIDCrashReporting.disable();
|
|
172
189
|
};
|
|
173
|
-
}, [enableCrashReporting]);
|
|
190
|
+
}, [enableCrashReporting, useSandbox]);
|
|
174
191
|
|
|
175
192
|
// `onResult` lives in a ref so the manager's `onExit` closure
|
|
176
193
|
// captures the latest partner callback without recreating the
|
|
@@ -178,6 +195,9 @@ export function FlowSuccessRenderer({
|
|
|
178
195
|
const onResultRef = useRef(configuration.onResult);
|
|
179
196
|
onResultRef.current = configuration.onResult;
|
|
180
197
|
|
|
198
|
+
// Latched by `handleExit` so an unmounted-before-finishing flow can deliver exactly once.
|
|
199
|
+
const finishedRef = useRef(false);
|
|
200
|
+
|
|
181
201
|
// Force-update reducer drives re-renders when the manager's cursor
|
|
182
202
|
// or captured artifacts change. Cheaper than mirroring state into
|
|
183
203
|
// React state — the manager is the single source of truth.
|
|
@@ -213,6 +233,7 @@ export function FlowSuccessRenderer({
|
|
|
213
233
|
|
|
214
234
|
const manager = useMemo(() => {
|
|
215
235
|
const handleExit = (reason: FlowExitReason) => {
|
|
236
|
+
finishedRef.current = true;
|
|
216
237
|
const onResult = onResultRef.current;
|
|
217
238
|
try {
|
|
218
239
|
if (reason.kind === "completed") {
|
|
@@ -264,6 +285,17 @@ export function FlowSuccessRenderer({
|
|
|
264
285
|
};
|
|
265
286
|
}, [manager]);
|
|
266
287
|
|
|
288
|
+
// A flow unmounted before finishing delivers a cancelled result exactly once.
|
|
289
|
+
useEffect(
|
|
290
|
+
() => () => {
|
|
291
|
+
if (!finishedRef.current) {
|
|
292
|
+
finishedRef.current = true;
|
|
293
|
+
onResultRef.current(cancelled());
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
[],
|
|
297
|
+
);
|
|
298
|
+
|
|
267
299
|
// Resolve localizations:
|
|
268
300
|
// 1. Explicit `configuration.localizations` (partner-set on the
|
|
269
301
|
// builder) — wins over everything.
|
|
@@ -272,13 +304,23 @@ export function FlowSuccessRenderer({
|
|
|
272
304
|
// registered locales with `UseSmileIDLocalizations.register(...)`.
|
|
273
305
|
// 3. English defaults — no provider mounted, screens read from
|
|
274
306
|
// the context's default English-only instance.
|
|
307
|
+
// A device locale change requires leaving the app, so re-reading on foreground is what makes a mid-flow switch observable.
|
|
308
|
+
const [localeEpoch, bumpLocaleEpoch] = useReducer((x: number) => x + 1, 0);
|
|
309
|
+
useEffect(() => {
|
|
310
|
+
const subscription = AppState.addEventListener("change", (nextState) => {
|
|
311
|
+
if (nextState === "active") bumpLocaleEpoch();
|
|
312
|
+
});
|
|
313
|
+
return () => subscription.remove();
|
|
314
|
+
}, []);
|
|
315
|
+
|
|
316
|
+
const activeLocaleKey = UseSmileIDLocalizations.activeLocaleKey;
|
|
275
317
|
const resolved = useMemo(() => {
|
|
276
318
|
if (configuration.localizations) return configuration.localizations;
|
|
277
319
|
if (UseSmileIDLocalizations.hasRegisteredLocales()) {
|
|
278
320
|
return UseSmileIDLocalizations.forActiveLocale();
|
|
279
321
|
}
|
|
280
322
|
return null;
|
|
281
|
-
}, [configuration.localizations]);
|
|
323
|
+
}, [configuration.localizations, activeLocaleKey, localeEpoch]);
|
|
282
324
|
|
|
283
325
|
const step = manager.currentScreen;
|
|
284
326
|
|
|
@@ -324,7 +366,16 @@ export function FlowSuccessRenderer({
|
|
|
324
366
|
// resolve — the context default (English) covers the no-provider
|
|
325
367
|
// path without an extra wrapper.
|
|
326
368
|
const themed = (
|
|
327
|
-
<UseSmileIDThemeProvider value={theme}>
|
|
369
|
+
<UseSmileIDThemeProvider value={theme}>
|
|
370
|
+
{fontsLoaded || fontWaitElapsed ? (
|
|
371
|
+
inner
|
|
372
|
+
) : (
|
|
373
|
+
<View
|
|
374
|
+
style={{ flex: 1, backgroundColor: theme.colors.background }}
|
|
375
|
+
testID="si_flow_font_gate"
|
|
376
|
+
/>
|
|
377
|
+
)}
|
|
378
|
+
</UseSmileIDThemeProvider>
|
|
328
379
|
);
|
|
329
380
|
|
|
330
381
|
if (resolved) {
|