@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
|
@@ -4,7 +4,9 @@ import type {
|
|
|
4
4
|
PartnerConfig,
|
|
5
5
|
} from "../../core/models/network_config";
|
|
6
6
|
import { SmileIDUrls } from "../smile_id_urls";
|
|
7
|
-
import type { ConsentInformation
|
|
7
|
+
import type { ConsentInformation } from "../../core/models/params";
|
|
8
|
+
import { userDetailsPayloadToWire } from "../model/user_details_payload";
|
|
9
|
+
import type { UserDetailsPayload } from "../model/user_details_payload";
|
|
8
10
|
import type {
|
|
9
11
|
BiometricKycRequest,
|
|
10
12
|
DocumentVerificationRequest,
|
|
@@ -87,11 +89,7 @@ export class SmileIDAPI {
|
|
|
87
89
|
request: SmartSelfieEnrollmentRequest,
|
|
88
90
|
): Promise<JobSubmissionResponse> {
|
|
89
91
|
const formData = this._buildSelfieFormData(request);
|
|
90
|
-
|
|
91
|
-
formData,
|
|
92
|
-
"user_details",
|
|
93
|
-
userDetailsToWire(request.userDetails),
|
|
94
|
-
);
|
|
92
|
+
appendUserDetailsPart(formData, request);
|
|
95
93
|
this._appendPartnerConfigParts(formData);
|
|
96
94
|
this._appendConsentAndMetadata(formData, request.consent, request.metadata);
|
|
97
95
|
return this._postMultipart("/v3/registration", formData);
|
|
@@ -108,11 +106,7 @@ export class SmileIDAPI {
|
|
|
108
106
|
): Promise<JobSubmissionResponse> {
|
|
109
107
|
const formData = this._buildSelfieFormData(request);
|
|
110
108
|
appendStringPart(formData, "user_id", request.userId);
|
|
111
|
-
|
|
112
|
-
formData,
|
|
113
|
-
"user_details",
|
|
114
|
-
userDetailsToWire(request.userDetails),
|
|
115
|
-
);
|
|
109
|
+
appendUserDetailsPart(formData, request);
|
|
116
110
|
this._appendPartnerConfigParts(formData);
|
|
117
111
|
this._appendConsentAndMetadata(formData, request.consent, request.metadata);
|
|
118
112
|
return this._postMultipart("/v3/authentication", formData);
|
|
@@ -126,11 +120,7 @@ export class SmileIDAPI {
|
|
|
126
120
|
// multipart/form-data`.
|
|
127
121
|
const formData = new FormData();
|
|
128
122
|
this._appendIdFields(formData, request.params);
|
|
129
|
-
|
|
130
|
-
formData,
|
|
131
|
-
"user_details",
|
|
132
|
-
userDetailsToWire(request.userDetails),
|
|
133
|
-
);
|
|
123
|
+
appendUserDetailsPart(formData, request);
|
|
134
124
|
if (request.params.bankCode) {
|
|
135
125
|
appendStringPart(formData, "bank_code", request.params.bankCode);
|
|
136
126
|
}
|
|
@@ -149,11 +139,7 @@ export class SmileIDAPI {
|
|
|
149
139
|
const useEnrolled = request.params.useEnrolledImage;
|
|
150
140
|
const formData = this._buildSelfieFormData(request);
|
|
151
141
|
this._appendIdFields(formData, request.params);
|
|
152
|
-
|
|
153
|
-
formData,
|
|
154
|
-
"user_details",
|
|
155
|
-
userDetailsToWire(request.userDetails),
|
|
156
|
-
);
|
|
142
|
+
appendUserDetailsPart(formData, request);
|
|
157
143
|
if (useEnrolled) {
|
|
158
144
|
appendStringPart(formData, "use_enrolled_image", "true");
|
|
159
145
|
}
|
|
@@ -194,11 +180,7 @@ export class SmileIDAPI {
|
|
|
194
180
|
if (request.idType) {
|
|
195
181
|
appendStringPart(formData, "id_type", request.idType);
|
|
196
182
|
}
|
|
197
|
-
|
|
198
|
-
formData,
|
|
199
|
-
"user_details",
|
|
200
|
-
userDetailsToWire(request.userDetails),
|
|
201
|
-
);
|
|
183
|
+
appendUserDetailsPart(formData, request);
|
|
202
184
|
this._appendPartnerConfigParts(formData);
|
|
203
185
|
this._appendConsentAndMetadata(formData, request.consent, request.metadata);
|
|
204
186
|
return this._postMultipart(url, formData);
|
|
@@ -306,6 +288,10 @@ export class SmileIDAPI {
|
|
|
306
288
|
* the part with `Content-Type: application/json` causes the parser
|
|
307
289
|
* to reject the part as "missing or invalid".
|
|
308
290
|
*
|
|
291
|
+
* `consent` is `undefined` only when the token binds complete
|
|
292
|
+
* consent and no local source exists — the part is then omitted
|
|
293
|
+
* entirely and the server injects the token's consent.
|
|
294
|
+
*
|
|
309
295
|
* The `metadata` snapshot is built by the request-construction site
|
|
310
296
|
* (the flow navigation manager) and travels on the request — this
|
|
311
297
|
* method serializes the snapshot it is handed rather than generating
|
|
@@ -313,19 +299,21 @@ export class SmileIDAPI {
|
|
|
313
299
|
*/
|
|
314
300
|
private _appendConsentAndMetadata(
|
|
315
301
|
formData: FormData,
|
|
316
|
-
consent: ConsentInformation,
|
|
302
|
+
consent: ConsentInformation | undefined,
|
|
317
303
|
metadata: UseSmileIDMetadata,
|
|
318
304
|
): void {
|
|
319
305
|
// Transform the JS camelCase shape into the canonical snake_case
|
|
320
306
|
// wire JSON before serialization. The server validates the wire
|
|
321
307
|
// shape exactly — `consent.granted` (snake_case via `granted`
|
|
322
308
|
// already) and `consent.granted_at` are required.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
309
|
+
if (consent !== undefined) {
|
|
310
|
+
appendJsonPart(formData, "consent", {
|
|
311
|
+
granted: consent.granted,
|
|
312
|
+
granted_at: consent.grantedAt,
|
|
313
|
+
notice_language: consent.noticeLanguage,
|
|
314
|
+
notice_privacy_policy_url: consent.noticePrivacyPolicyUrl,
|
|
315
|
+
});
|
|
316
|
+
}
|
|
329
317
|
appendJsonPart(formData, "metadata", metadata.toEntries());
|
|
330
318
|
}
|
|
331
319
|
|
|
@@ -386,23 +374,22 @@ function mapJobSubmissionResponse(data: unknown): JobSubmissionResponse {
|
|
|
386
374
|
}
|
|
387
375
|
|
|
388
376
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
377
|
+
* Append the `user_details` JSON multipart part. The payload arrives
|
|
378
|
+
* pre-reduced from assembly (token-bound fields already dropped); the
|
|
379
|
+
* part is omitted entirely when nothing remains — or when `userDetails`
|
|
380
|
+
* was legitimately omitted because the token binds every required field.
|
|
391
381
|
*/
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
wire["phone_number"] = trimmedPhone;
|
|
404
|
-
}
|
|
405
|
-
return wire;
|
|
382
|
+
function appendUserDetailsPart(
|
|
383
|
+
formData: FormData,
|
|
384
|
+
request: {
|
|
385
|
+
readonly userDetails?: UserDetailsPayload;
|
|
386
|
+
},
|
|
387
|
+
): void {
|
|
388
|
+
const details = request.userDetails;
|
|
389
|
+
if (details === undefined) return;
|
|
390
|
+
const wire = userDetailsPayloadToWire(details);
|
|
391
|
+
if (Object.keys(wire).length === 0) return;
|
|
392
|
+
appendJsonPart(formData, "user_details", wire);
|
|
406
393
|
}
|
|
407
394
|
|
|
408
395
|
/**
|
|
@@ -125,7 +125,9 @@ describe("PayloadCompressor with real JPEG bytes", () => {
|
|
|
125
125
|
|
|
126
126
|
it(
|
|
127
127
|
"compresses an inflated selfie+liveness bundle under the caps and keeps files decodable",
|
|
128
|
-
|
|
128
|
+
// Real JPEG (re)compression is CPU-heavy; under v8 coverage instrumentation on CI it runs
|
|
129
|
+
// ~2.5x slower (~120s), so allow generous headroom over the ~120s per-test CI runtime.
|
|
130
|
+
{ timeout: 300_000 },
|
|
129
131
|
async () => {
|
|
130
132
|
const selfieUri = "file:///capture/selfie.jpg";
|
|
131
133
|
const livenessUri = "file:///capture/liveness.jpg";
|
|
@@ -172,7 +174,9 @@ describe("PayloadCompressor with real JPEG bytes", () => {
|
|
|
172
174
|
|
|
173
175
|
it(
|
|
174
176
|
"compresses a document-heavy bundle back-first and lands under the guarantee caps",
|
|
175
|
-
|
|
177
|
+
// Real JPEG (re)compression is CPU-heavy; under v8 coverage instrumentation on CI it runs
|
|
178
|
+
// ~2.5x slower (~120s), so allow generous headroom over the ~120s per-test CI runtime.
|
|
179
|
+
{ timeout: 300_000 },
|
|
176
180
|
async () => {
|
|
177
181
|
const frontUri = "file:///capture/front.jpg";
|
|
178
182
|
const backUri = "file:///capture/back.jpg";
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { UseSmileIDNetworkBuilder } from "../use_smile_id_network_builder";
|
|
4
|
+
import { JobType } from "../../../core/models/job_type";
|
|
5
|
+
import {
|
|
6
|
+
LogLevel,
|
|
7
|
+
RetryStrategy,
|
|
8
|
+
type Interceptor,
|
|
9
|
+
} from "../../../core/models/network_config";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The network DSL's top level: every block is lazily constructed, a
|
|
13
|
+
* repeated block keeps mutating the same receiver, and `build()` emits
|
|
14
|
+
* only the blocks the partner actually opened — an eagerly-created
|
|
15
|
+
* builder would ship defaults the partner never asked for.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
describe("UseSmileIDNetworkBuilder", () => {
|
|
19
|
+
it("emits an empty config when no block is opened", () => {
|
|
20
|
+
expect(new UseSmileIDNetworkBuilder().build()).toEqual({});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("freezes the built configuration", () => {
|
|
24
|
+
const built = new UseSmileIDNetworkBuilder().build();
|
|
25
|
+
expect(Object.isFrozen(built)).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("builds the config block from the partner's identity and job type", () => {
|
|
29
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
30
|
+
builder.config((config) => {
|
|
31
|
+
config.jobType = JobType.enhancedKyc;
|
|
32
|
+
config.token = "jwt-token";
|
|
33
|
+
config.partnerConfig((partner) => {
|
|
34
|
+
partner.partnerId = "0001";
|
|
35
|
+
partner.useSandbox = true;
|
|
36
|
+
});
|
|
37
|
+
config.logging((logging) => {
|
|
38
|
+
logging.level = LogLevel.body;
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const built = builder.build();
|
|
43
|
+
expect(built.config?.jobType).toBe(JobType.enhancedKyc);
|
|
44
|
+
expect(built.config?.token).toBe("jwt-token");
|
|
45
|
+
expect(built.config?.partnerConfig?.partnerId).toBe("0001");
|
|
46
|
+
expect(built.config?.partnerConfig?.useSandbox).toBe(true);
|
|
47
|
+
expect(built.config?.logging?.level).toBe(LogLevel.body);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("builds per-phase timeouts", () => {
|
|
51
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
52
|
+
builder.timeouts((timeouts) => {
|
|
53
|
+
timeouts.connect = 5;
|
|
54
|
+
timeouts.read = 10;
|
|
55
|
+
timeouts.write = 15;
|
|
56
|
+
timeouts.call = 20;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
expect(builder.build().timeouts).toEqual({
|
|
60
|
+
connect: 5,
|
|
61
|
+
read: 10,
|
|
62
|
+
write: 15,
|
|
63
|
+
call: 20,
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("builds the cache block, including the megabytes helper", () => {
|
|
68
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
69
|
+
builder.cache((cache) => {
|
|
70
|
+
cache.enabled = false;
|
|
71
|
+
cache.maxSize = cache.megabytes(8);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(builder.build().cache).toEqual({
|
|
75
|
+
enabled: false,
|
|
76
|
+
maxSize: 8 * 1024 * 1024,
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("builds the retry block", () => {
|
|
81
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
82
|
+
builder.retry((retry) => {
|
|
83
|
+
retry.strategy = RetryStrategy.exponential;
|
|
84
|
+
retry.maxAttempts = 4;
|
|
85
|
+
retry.initialDelay = 2;
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const retry = builder.build().retry;
|
|
89
|
+
expect(retry?.strategy).toBe(RetryStrategy.exponential);
|
|
90
|
+
expect(retry?.maxAttempts).toBe(4);
|
|
91
|
+
expect(retry?.initialDelay).toBe(2);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("builds the interceptor chain in registration order", () => {
|
|
95
|
+
const custom: Interceptor = { attach: vi.fn() };
|
|
96
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
97
|
+
builder.interceptors((interceptors) => {
|
|
98
|
+
interceptors.add(interceptors.gzip());
|
|
99
|
+
interceptors.add(custom);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const chain = builder.build().interceptors;
|
|
103
|
+
expect(chain).toHaveLength(2);
|
|
104
|
+
expect(chain?.[1]).toBe(custom);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("reuses each block's builder across repeated calls", () => {
|
|
108
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
109
|
+
const seen: unknown[] = [];
|
|
110
|
+
|
|
111
|
+
builder.cache((cache) => {
|
|
112
|
+
seen.push(cache);
|
|
113
|
+
cache.maxSize = 1;
|
|
114
|
+
});
|
|
115
|
+
builder.cache((cache) => {
|
|
116
|
+
seen.push(cache);
|
|
117
|
+
cache.enabled = false;
|
|
118
|
+
});
|
|
119
|
+
builder.retry((retry) => {
|
|
120
|
+
seen.push(retry);
|
|
121
|
+
retry.strategy = RetryStrategy.fixed;
|
|
122
|
+
});
|
|
123
|
+
builder.retry((retry) => {
|
|
124
|
+
seen.push(retry);
|
|
125
|
+
retry.fixedDelay = 3;
|
|
126
|
+
});
|
|
127
|
+
builder.interceptors((interceptors) => {
|
|
128
|
+
seen.push(interceptors);
|
|
129
|
+
interceptors.add({ attach: vi.fn() });
|
|
130
|
+
});
|
|
131
|
+
builder.interceptors((interceptors) => {
|
|
132
|
+
seen.push(interceptors);
|
|
133
|
+
interceptors.add({ attach: vi.fn() });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(seen[0]).toBe(seen[1]);
|
|
137
|
+
expect(seen[2]).toBe(seen[3]);
|
|
138
|
+
expect(seen[4]).toBe(seen[5]);
|
|
139
|
+
|
|
140
|
+
const built = builder.build();
|
|
141
|
+
// Both mutations survived because the receiver was reused.
|
|
142
|
+
expect(built.cache).toEqual({ enabled: false, maxSize: 1 });
|
|
143
|
+
expect(built.retry?.strategy).toBe(RetryStrategy.fixed);
|
|
144
|
+
expect(built.retry?.fixedDelay).toBe(3);
|
|
145
|
+
expect(built.interceptors).toHaveLength(2);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("omits blocks the partner never opened", () => {
|
|
149
|
+
const builder = new UseSmileIDNetworkBuilder();
|
|
150
|
+
builder.cache((cache) => {
|
|
151
|
+
cache.enabled = false;
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const built = builder.build();
|
|
155
|
+
expect(Object.keys(built)).toEqual(["cache"]);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { CacheConfigBuilder } from "../cache_config_builder";
|
|
4
|
+
import { RetryConfigBuilder, RetryStrategy } from "../retry_config_builder";
|
|
5
|
+
|
|
6
|
+
/** Retry/cache DSL builders: pin the documented defaults, frozen output, and overrides. */
|
|
7
|
+
describe("RetryConfigBuilder", () => {
|
|
8
|
+
it("builds a frozen config with the documented defaults", () => {
|
|
9
|
+
const config = new RetryConfigBuilder().build();
|
|
10
|
+
|
|
11
|
+
expect(config).toEqual({
|
|
12
|
+
maxAttempts: 1,
|
|
13
|
+
strategy: RetryStrategy.none,
|
|
14
|
+
initialDelay: 1,
|
|
15
|
+
multiplier: 2,
|
|
16
|
+
maxDelay: 32,
|
|
17
|
+
jitter: 0.2,
|
|
18
|
+
fixedDelay: 1,
|
|
19
|
+
});
|
|
20
|
+
expect(Object.isFrozen(config)).toBe(true);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("forces maxAttempts to 1 for none, whatever the field says", () => {
|
|
24
|
+
const builder = new RetryConfigBuilder();
|
|
25
|
+
builder.maxAttempts = 5;
|
|
26
|
+
|
|
27
|
+
expect(builder.build().maxAttempts).toBe(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("applies the attempt budget once a strategy opts in", () => {
|
|
31
|
+
const builder = new RetryConfigBuilder();
|
|
32
|
+
builder.strategy = RetryStrategy.exponential;
|
|
33
|
+
|
|
34
|
+
expect(builder.build().maxAttempts).toBe(3);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("carries overridden fields through build()", () => {
|
|
38
|
+
const builder = new RetryConfigBuilder();
|
|
39
|
+
builder.maxAttempts = 5;
|
|
40
|
+
builder.strategy = RetryStrategy.fixed;
|
|
41
|
+
builder.fixedDelay = 4;
|
|
42
|
+
|
|
43
|
+
const config = builder.build();
|
|
44
|
+
|
|
45
|
+
expect(config.maxAttempts).toBe(5);
|
|
46
|
+
expect(config.strategy).toBe(RetryStrategy.fixed);
|
|
47
|
+
expect(config.fixedDelay).toBe(4);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
describe("CacheConfigBuilder", () => {
|
|
52
|
+
it("defaults to enabled with a 50 MB ceiling", () => {
|
|
53
|
+
const config = new CacheConfigBuilder().build();
|
|
54
|
+
|
|
55
|
+
expect(config).toEqual({ enabled: true, maxSize: 50 * 1024 * 1024 });
|
|
56
|
+
expect(Object.isFrozen(config)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("megabytes() converts MB to bytes", () => {
|
|
60
|
+
expect(new CacheConfigBuilder().megabytes(10)).toBe(10 * 1024 * 1024);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -8,16 +8,16 @@ export { RetryStrategy };
|
|
|
8
8
|
/**
|
|
9
9
|
* DSL builder for request retry behaviour.
|
|
10
10
|
*
|
|
11
|
-
* Defaults to
|
|
12
|
-
* 32 s ceiling, ±20% jitter.
|
|
13
|
-
* `strategy = RetryStrategy.fixed`.
|
|
11
|
+
* Defaults to no retry. Set `strategy` to opt in, and the remaining fields
|
|
12
|
+
* then apply: 3 attempts, 1 s initial delay, 32 s ceiling, ±20% jitter.
|
|
13
|
+
* `fixedDelay` is honoured only when `strategy = RetryStrategy.fixed`.
|
|
14
14
|
*/
|
|
15
15
|
export class RetryConfigBuilder {
|
|
16
|
-
/**
|
|
16
|
+
/** Attempts per request including the first; applies once `strategy` opts in. */
|
|
17
17
|
maxAttempts = 3;
|
|
18
18
|
|
|
19
19
|
/** Backoff strategy between attempts: exponential, fixed, or none. */
|
|
20
|
-
strategy: RetryStrategy = RetryStrategy.
|
|
20
|
+
strategy: RetryStrategy = RetryStrategy.none;
|
|
21
21
|
|
|
22
22
|
/** Delay before the first retry, in seconds (exponential strategy). */
|
|
23
23
|
initialDelay = 1;
|
|
@@ -45,7 +45,7 @@ export class RetryConfigBuilder {
|
|
|
45
45
|
|
|
46
46
|
build(): RetryConfig {
|
|
47
47
|
return Object.freeze({
|
|
48
|
-
maxAttempts: this.maxAttempts,
|
|
48
|
+
maxAttempts: this.strategy === RetryStrategy.none ? 1 : this.maxAttempts,
|
|
49
49
|
strategy: this.strategy,
|
|
50
50
|
initialDelay: this.initialDelay,
|
|
51
51
|
multiplier: this.multiplier,
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
AxiosError,
|
|
4
|
+
type AxiosResponse,
|
|
5
|
+
type InternalAxiosRequestConfig,
|
|
6
|
+
} from "axios";
|
|
7
|
+
|
|
8
|
+
import { JobType } from "../../../core/models/job_type";
|
|
9
|
+
import type { NetworkConfig } from "../../../core/models/network_config";
|
|
10
|
+
import { makeNetworkingFactory } from "../networking_factory";
|
|
11
|
+
import { SmileIDCryptoManager } from "../../security/smile_id_crypto_manager";
|
|
12
|
+
import {
|
|
13
|
+
REQUEST_MAC_HEADER,
|
|
14
|
+
REQUEST_MAC_OPT_IN_KEY,
|
|
15
|
+
REQUEST_TIMESTAMP_HEADER,
|
|
16
|
+
} from "../../api/interceptors/smile_request_mac_interceptor";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Standing-guard composition test for token-refresh × gzip × request-MAC on a
|
|
20
|
+
* 401 → refresh → retry. `TokenRefreshInterceptor` re-issues the config, re-running the whole
|
|
21
|
+
* chain (auth re-stamps the token, MAC re-signs, gzip re-stamps `Accept-Encoding`). If a future
|
|
22
|
+
* change made gzip replace `config.data` with compressed bytes, the MAC would sign a body the
|
|
23
|
+
* canonicalizer no longer sees as FormData → `{}`. Pins the retried MAC against the uncompressed
|
|
24
|
+
* body so that regression fails loud.
|
|
25
|
+
*/
|
|
26
|
+
describe("token-refresh x gzip x request-MAC composition", () => {
|
|
27
|
+
const initialToken = "token-1";
|
|
28
|
+
const refreshedToken = "token-2";
|
|
29
|
+
const signingSecret = "test-signing-secret";
|
|
30
|
+
const cryptoManager = new SmileIDCryptoManager(signingSecret);
|
|
31
|
+
|
|
32
|
+
type FormDataPart = { fieldName: string; string?: string; uri?: string };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Stand-in for RN's `FormData`. Besides `getParts()` (what the canonicalizer reads), it
|
|
36
|
+
* satisfies axios's `isFormData()` (`Symbol.toStringTag` + `append`) so axios's request
|
|
37
|
+
* transform leaves it untouched instead of JSON-stringifying it.
|
|
38
|
+
*/
|
|
39
|
+
class FakeFormData {
|
|
40
|
+
constructor(private readonly parts: readonly FormDataPart[]) {}
|
|
41
|
+
get [Symbol.toStringTag](): string {
|
|
42
|
+
return "FormData";
|
|
43
|
+
}
|
|
44
|
+
append(): void {
|
|
45
|
+
/* not exercised — only presence is checked by axios's isFormData() */
|
|
46
|
+
}
|
|
47
|
+
getParts(): readonly FormDataPart[] {
|
|
48
|
+
return this.parts;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function submissionBody(): FakeFormData {
|
|
53
|
+
return new FakeFormData([
|
|
54
|
+
{ fieldName: "country", string: "NG" },
|
|
55
|
+
{ fieldName: "partner_params", string: '{"flow":"enhanced"}' },
|
|
56
|
+
{
|
|
57
|
+
fieldName: "selfie_image",
|
|
58
|
+
uri: "data:application/octet-stream;base64,c2VsZmll",
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Flattens the observed AxiosHeaders to a lower-cased record, matching how
|
|
64
|
+
* RequestCanonicalizer lower-cases header names before signing. */
|
|
65
|
+
function headersRecord(
|
|
66
|
+
headers: InternalAxiosRequestConfig["headers"],
|
|
67
|
+
): Record<string, string> {
|
|
68
|
+
const out: Record<string, string> = {};
|
|
69
|
+
for (const [key, value] of Object.entries(
|
|
70
|
+
headers as unknown as Record<string, unknown>,
|
|
71
|
+
)) {
|
|
72
|
+
if (value != null) out[key.toLowerCase()] = String(value);
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function makeConfig(): NetworkConfig {
|
|
78
|
+
return {
|
|
79
|
+
config: {
|
|
80
|
+
jobType: JobType.smartSelfieEnrollment,
|
|
81
|
+
token: initialToken,
|
|
82
|
+
onTokenExpired: async () => refreshedToken,
|
|
83
|
+
partnerConfig: {
|
|
84
|
+
partnerId: "partner-1",
|
|
85
|
+
callbackUrl: "https://partner.example.com/cb",
|
|
86
|
+
useSandbox: true,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
it("signs the refresh-retried request's MAC over the uncompressed submission body", async () => {
|
|
93
|
+
const requestsSeen: InternalAxiosRequestConfig[] = [];
|
|
94
|
+
const { client } = makeNetworkingFactory(makeConfig(), () => signingSecret);
|
|
95
|
+
|
|
96
|
+
// 401 first, 200 on the retry. A custom adapter bypasses axios's validateStatus, so it
|
|
97
|
+
// must reject non-2xx itself for TokenRefreshInterceptor's error handler to see it.
|
|
98
|
+
client.defaults.adapter = (
|
|
99
|
+
requestConfig: InternalAxiosRequestConfig,
|
|
100
|
+
): Promise<AxiosResponse> => {
|
|
101
|
+
requestsSeen.push(requestConfig);
|
|
102
|
+
const isFirstCall = requestsSeen.length === 1;
|
|
103
|
+
const response: AxiosResponse = {
|
|
104
|
+
status: isFirstCall ? 401 : 200,
|
|
105
|
+
statusText: isFirstCall ? "Unauthorized" : "OK",
|
|
106
|
+
data: isFirstCall ? { error: "unauthorized" } : { jobId: "job-1" },
|
|
107
|
+
headers: {},
|
|
108
|
+
config: requestConfig,
|
|
109
|
+
};
|
|
110
|
+
if (isFirstCall) {
|
|
111
|
+
return Promise.reject(
|
|
112
|
+
new AxiosError(
|
|
113
|
+
"Request failed with status code 401",
|
|
114
|
+
AxiosError.ERR_BAD_REQUEST,
|
|
115
|
+
requestConfig,
|
|
116
|
+
undefined,
|
|
117
|
+
response,
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return Promise.resolve(response);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const body = submissionBody();
|
|
125
|
+
const response = await client.post("/v3/enhanced_kyc", body, {
|
|
126
|
+
[REQUEST_MAC_OPT_IN_KEY]: true,
|
|
127
|
+
} as Partial<InternalAxiosRequestConfig>);
|
|
128
|
+
|
|
129
|
+
expect(response.status).toBe(200);
|
|
130
|
+
expect(requestsSeen).toHaveLength(2);
|
|
131
|
+
|
|
132
|
+
const retried = requestsSeen[1]!;
|
|
133
|
+
const retriedHeaders = headersRecord(retried.headers);
|
|
134
|
+
|
|
135
|
+
expect(retriedHeaders["smileid-token"]).toBe(refreshedToken);
|
|
136
|
+
expect(retriedHeaders["accept-encoding"]).toBe("gzip");
|
|
137
|
+
// gzip is hint-only — it never compresses the body, so no Content-Encoding claim.
|
|
138
|
+
expect(retriedHeaders["content-encoding"]).toBeUndefined();
|
|
139
|
+
|
|
140
|
+
const retriedMac = retriedHeaders[REQUEST_MAC_HEADER];
|
|
141
|
+
const retriedTimestamp = retriedHeaders[REQUEST_TIMESTAMP_HEADER];
|
|
142
|
+
expect(retriedMac).toBeDefined();
|
|
143
|
+
expect(retriedTimestamp).toBeDefined();
|
|
144
|
+
|
|
145
|
+
// Recompute the server-verified MAC over the retried headers and the real body — gzip
|
|
146
|
+
// is hint-only (never replaces config.data), so `body` is the bytes that shipped.
|
|
147
|
+
const expectedMac = await cryptoManager.sign(
|
|
148
|
+
retriedTimestamp!,
|
|
149
|
+
retriedHeaders,
|
|
150
|
+
body,
|
|
151
|
+
);
|
|
152
|
+
expect(retriedMac).toBe(expectedMac);
|
|
153
|
+
|
|
154
|
+
// And it must not match a MAC computed against an empty body — the
|
|
155
|
+
// signature of the regression this test guards against.
|
|
156
|
+
const emptyBodyMac = await cryptoManager.sign(
|
|
157
|
+
retriedTimestamp!,
|
|
158
|
+
retriedHeaders,
|
|
159
|
+
undefined,
|
|
160
|
+
);
|
|
161
|
+
expect(retriedMac).not.toBe(emptyBodyMac);
|
|
162
|
+
});
|
|
163
|
+
});
|