@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,203 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import Module from "node:module";
|
|
3
|
+
|
|
4
|
+
import { SigningCrypto, base64Encode, utf8Bytes } from "../signing_crypto";
|
|
5
|
+
import { SmileIDCryptoError } from "../smile_id_crypto_error";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Provider resolution for request signing. Node supplies a global
|
|
9
|
+
* `crypto.subtle`, so the on-device path — the lazy
|
|
10
|
+
* `require("react-native-quick-crypto")` — is only reachable with the
|
|
11
|
+
* global removed and the CommonJS loader intercepted. `resolveSubtle`
|
|
12
|
+
* caches the first provider it finds, so these tests are ordered:
|
|
13
|
+
* the not-found case runs before anything is cached.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
type SubtleStub = {
|
|
17
|
+
importKey: ReturnType<typeof vi.fn>;
|
|
18
|
+
deriveBits: ReturnType<typeof vi.fn>;
|
|
19
|
+
sign: ReturnType<typeof vi.fn>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type Loader = (id: string, parent: unknown, isMain: boolean) => unknown;
|
|
23
|
+
const loaderHost = Module as unknown as { _load: Loader };
|
|
24
|
+
const originalLoad: Loader = loaderHost._load;
|
|
25
|
+
|
|
26
|
+
const QUICK_CRYPTO = "react-native-quick-crypto";
|
|
27
|
+
|
|
28
|
+
let quickCryptoExports: unknown | null = null;
|
|
29
|
+
let cryptoDescriptor: PropertyDescriptor | undefined;
|
|
30
|
+
|
|
31
|
+
function makeSubtle(): SubtleStub {
|
|
32
|
+
return {
|
|
33
|
+
importKey: vi.fn(async () => ({ handle: "key" })),
|
|
34
|
+
deriveBits: vi.fn(async () => new Uint8Array(32).fill(7).buffer),
|
|
35
|
+
sign: vi.fn(async () => new Uint8Array(32).fill(9).buffer),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeAll(() => {
|
|
40
|
+
// Hermes exposes no global `crypto.subtle`; reproduce that here so the
|
|
41
|
+
// native-provider branch is the one under test.
|
|
42
|
+
cryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, "crypto");
|
|
43
|
+
Object.defineProperty(globalThis, "crypto", {
|
|
44
|
+
value: undefined,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
});
|
|
48
|
+
vi.spyOn(loaderHost, "_load").mockImplementation((id, parent, isMain) => {
|
|
49
|
+
if (id === QUICK_CRYPTO) {
|
|
50
|
+
if (quickCryptoExports === null) {
|
|
51
|
+
const error = new Error(
|
|
52
|
+
`Cannot find module '${QUICK_CRYPTO}'`,
|
|
53
|
+
) as Error & {
|
|
54
|
+
code: string;
|
|
55
|
+
};
|
|
56
|
+
error.code = "MODULE_NOT_FOUND";
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
return quickCryptoExports;
|
|
60
|
+
}
|
|
61
|
+
return originalLoad.call(Module, id, parent, isMain);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterAll(() => {
|
|
66
|
+
vi.restoreAllMocks();
|
|
67
|
+
if (cryptoDescriptor) {
|
|
68
|
+
Object.defineProperty(globalThis, "crypto", cryptoDescriptor);
|
|
69
|
+
} else {
|
|
70
|
+
delete (globalThis as { crypto?: unknown }).crypto;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("SigningCrypto — encoding helpers", () => {
|
|
75
|
+
it("UTF-8 encodes multi-byte text", () => {
|
|
76
|
+
expect(Array.from(utf8Bytes("é"))).toEqual([0xc3, 0xa9]);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("base64-encodes with the standard alphabet and padding", () => {
|
|
80
|
+
expect(base64Encode(utf8Bytes("any carnal pleasure"))).toBe(
|
|
81
|
+
"YW55IGNhcm5hbCBwbGVhc3VyZQ==",
|
|
82
|
+
);
|
|
83
|
+
expect(base64Encode(utf8Bytes("any carnal pleasur"))).toBe(
|
|
84
|
+
"YW55IGNhcm5hbCBwbGVhc3Vy",
|
|
85
|
+
);
|
|
86
|
+
expect(base64Encode(utf8Bytes("any carnal pleasu"))).toBe(
|
|
87
|
+
"YW55IGNhcm5hbCBwbGVhc3U=",
|
|
88
|
+
);
|
|
89
|
+
expect(base64Encode(new Uint8Array(0))).toBe("");
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Ordered: the not-found case must run before a provider is cached.
|
|
94
|
+
describe("SigningCrypto — no provider available", () => {
|
|
95
|
+
it("fails key derivation with actionable install guidance", async () => {
|
|
96
|
+
quickCryptoExports = null;
|
|
97
|
+
|
|
98
|
+
await expect(
|
|
99
|
+
SigningCrypto.deriveKey("secret", new Uint8Array([1, 2, 3])),
|
|
100
|
+
).rejects.toBeInstanceOf(SmileIDCryptoError);
|
|
101
|
+
await expect(
|
|
102
|
+
SigningCrypto.deriveKey("secret", new Uint8Array([1, 2, 3])),
|
|
103
|
+
).rejects.toThrow(/react-native-quick-crypto/);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("fails MAC computation the same way", async () => {
|
|
107
|
+
quickCryptoExports = null;
|
|
108
|
+
|
|
109
|
+
await expect(
|
|
110
|
+
SigningCrypto.hmacSha256(new Uint8Array(32), new Uint8Array([1])),
|
|
111
|
+
).rejects.toThrow(/No Web Crypto provider available/);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe("SigningCrypto — native provider", () => {
|
|
116
|
+
const subtle = makeSubtle();
|
|
117
|
+
|
|
118
|
+
beforeAll(() => {
|
|
119
|
+
// Deepest of the four export shapes the package is read across.
|
|
120
|
+
quickCryptoExports = { default: { webcrypto: { subtle } } };
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("derives a 32-byte key through the native subtle", async () => {
|
|
124
|
+
const salt = new Uint8Array([9, 8, 7]);
|
|
125
|
+
const key = await SigningCrypto.deriveKey("secret", salt);
|
|
126
|
+
|
|
127
|
+
expect(key).toHaveLength(32);
|
|
128
|
+
expect(subtle.importKey).toHaveBeenCalledWith(
|
|
129
|
+
"raw",
|
|
130
|
+
expect.anything(),
|
|
131
|
+
"PBKDF2",
|
|
132
|
+
false,
|
|
133
|
+
["deriveBits"],
|
|
134
|
+
);
|
|
135
|
+
const deriveArgs = subtle.deriveBits.mock.calls[0] as [
|
|
136
|
+
{ name: string; iterations: number; hash: string; salt: ArrayBuffer },
|
|
137
|
+
unknown,
|
|
138
|
+
number,
|
|
139
|
+
];
|
|
140
|
+
expect(deriveArgs[0].name).toBe("PBKDF2");
|
|
141
|
+
expect(deriveArgs[0].iterations).toBe(200000);
|
|
142
|
+
expect(deriveArgs[2]).toBe(256);
|
|
143
|
+
// The salt crosses as its exact bytes, not the whole backing buffer.
|
|
144
|
+
expect(Array.from(new Uint8Array(deriveArgs[0].salt))).toEqual([9, 8, 7]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("computes an HMAC through the native subtle", async () => {
|
|
148
|
+
const mac = await SigningCrypto.hmacSha256(
|
|
149
|
+
new Uint8Array(32).fill(1),
|
|
150
|
+
utf8Bytes("payload"),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
expect(mac).toHaveLength(32);
|
|
154
|
+
expect(subtle.sign).toHaveBeenCalledWith(
|
|
155
|
+
"HMAC",
|
|
156
|
+
expect.anything(),
|
|
157
|
+
expect.anything(),
|
|
158
|
+
);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("passes only the view's own bytes when the input is a subarray", async () => {
|
|
162
|
+
const backing = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7]);
|
|
163
|
+
subtle.sign.mockClear();
|
|
164
|
+
await SigningCrypto.hmacSha256(new Uint8Array(32), backing.subarray(2, 5));
|
|
165
|
+
|
|
166
|
+
const dataBuffer = subtle.sign.mock.calls[0]?.[2] as ArrayBuffer;
|
|
167
|
+
expect(Array.from(new Uint8Array(dataBuffer))).toEqual([2, 3, 4]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("wraps a derivation failure in SmileIDCryptoError", async () => {
|
|
171
|
+
subtle.deriveBits.mockRejectedValueOnce(new Error("openssl exploded"));
|
|
172
|
+
|
|
173
|
+
await expect(
|
|
174
|
+
SigningCrypto.deriveKey("secret", new Uint8Array([1])),
|
|
175
|
+
).rejects.toThrow(/Key derivation failed: .*openssl exploded/);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("rethrows an already-typed crypto error from deriveKey unchanged", async () => {
|
|
179
|
+
const typed = new SmileIDCryptoError("key material rejected");
|
|
180
|
+
subtle.importKey.mockRejectedValueOnce(typed);
|
|
181
|
+
|
|
182
|
+
await expect(
|
|
183
|
+
SigningCrypto.deriveKey("secret", new Uint8Array([1])),
|
|
184
|
+
).rejects.toBe(typed);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("wraps a MAC failure in SmileIDCryptoError", async () => {
|
|
188
|
+
subtle.sign.mockRejectedValueOnce(new Error("bad key handle"));
|
|
189
|
+
|
|
190
|
+
await expect(
|
|
191
|
+
SigningCrypto.hmacSha256(new Uint8Array(32), new Uint8Array([1])),
|
|
192
|
+
).rejects.toThrow(/MAC computation failed: .*bad key handle/);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("rethrows an already-typed crypto error from hmacSha256 unchanged", async () => {
|
|
196
|
+
const typed = new SmileIDCryptoError("hmac key rejected");
|
|
197
|
+
subtle.importKey.mockRejectedValueOnce(typed);
|
|
198
|
+
|
|
199
|
+
await expect(
|
|
200
|
+
SigningCrypto.hmacSha256(new Uint8Array(32), new Uint8Array([1])),
|
|
201
|
+
).rejects.toBe(typed);
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* kept so the production host can diverge without touching call sites.
|
|
6
6
|
*/
|
|
7
7
|
export const SmileIDUrls = {
|
|
8
|
-
sandbox: "https://
|
|
8
|
+
sandbox: "https://testapi.smileidentity.com/",
|
|
9
9
|
production: "https://api.smileidentity.com/",
|
|
10
10
|
forConfig(useSandbox: boolean): string {
|
|
11
11
|
return useSandbox ? this.sandbox : this.production;
|
|
@@ -154,4 +154,29 @@ describe("withRetry", () => {
|
|
|
154
154
|
});
|
|
155
155
|
expect(block).toHaveBeenCalledTimes(3); // maxAttempts
|
|
156
156
|
});
|
|
157
|
+
|
|
158
|
+
it('strategy="none" never retries, even with an attempt budget', async () => {
|
|
159
|
+
// A config can carry both; the strategy wins.
|
|
160
|
+
const block = vi.fn().mockRejectedValue(makeAxiosError(503));
|
|
161
|
+
const config: RetryConfig = {
|
|
162
|
+
...exponentialConfig,
|
|
163
|
+
strategy: RetryStrategy.none,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
await expect(withRetry(config, block)).rejects.toMatchObject({
|
|
167
|
+
response: { status: 503 },
|
|
168
|
+
});
|
|
169
|
+
expect(block).toHaveBeenCalledTimes(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("makes one attempt and surfaces the real error when maxAttempts is 0", async () => {
|
|
173
|
+
// `attempt < 0` never ran the body, so the old code rejected with `undefined`.
|
|
174
|
+
const block = vi.fn().mockRejectedValue(makeAxiosError(503));
|
|
175
|
+
const config: RetryConfig = { ...exponentialConfig, maxAttempts: 0 };
|
|
176
|
+
|
|
177
|
+
await expect(withRetry(config, block)).rejects.toMatchObject({
|
|
178
|
+
response: { status: 503 },
|
|
179
|
+
});
|
|
180
|
+
expect(block).toHaveBeenCalledTimes(1);
|
|
181
|
+
});
|
|
157
182
|
});
|
|
@@ -3,6 +3,10 @@ import {
|
|
|
3
3
|
decodeSmileIDToken,
|
|
4
4
|
decodeSecurityPolicyBitCode,
|
|
5
5
|
} from "../use_smile_id_token_decoder";
|
|
6
|
+
import {
|
|
7
|
+
isTokenConsentComplete,
|
|
8
|
+
bindsRequiredUserDetails,
|
|
9
|
+
} from "../../model/token_payload";
|
|
6
10
|
|
|
7
11
|
// ---------------------------------------------------------------------------
|
|
8
12
|
// Helper — mint a structurally valid JWT with a caller-supplied payload.
|
|
@@ -243,9 +247,184 @@ describe("decodeSmileIDToken — parses a valid sdk_config claim", () => {
|
|
|
243
247
|
makeJwt({}),
|
|
244
248
|
makeJwt({ sdk_config: {} }),
|
|
245
249
|
makeJwt({ sdk_config: { selfie: { quality: 90 } } }),
|
|
250
|
+
makeJwt({ payload: "not-an-object" }),
|
|
251
|
+
makeJwt({ payload: { consent: { granted: "yes" } } }),
|
|
246
252
|
];
|
|
247
253
|
for (const input of inputs) {
|
|
248
254
|
expect(() => decodeSmileIDToken(input)).not.toThrow();
|
|
249
255
|
}
|
|
250
256
|
});
|
|
251
257
|
});
|
|
258
|
+
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
// Token-bound user details & consent — the top-level `payload` claim
|
|
261
|
+
// (a sibling of `sdk_config`). Values are opaque vault references; only
|
|
262
|
+
// key presence is meaningful. Consent subfields are plaintext.
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
describe("decodeSmileIDToken — payload claim (token-bound user details & consent)", () => {
|
|
265
|
+
it("parses a full payload claim into all four presence flags plus complete consent", () => {
|
|
266
|
+
const token = makeJwt({
|
|
267
|
+
payload: {
|
|
268
|
+
given_names: "pii_abc",
|
|
269
|
+
last_name: "pii_def",
|
|
270
|
+
email: "pii_ghi",
|
|
271
|
+
phone_number: "pii_jkl",
|
|
272
|
+
consent: {
|
|
273
|
+
granted: true,
|
|
274
|
+
granted_at: "2026-01-01T09:00:00.000Z",
|
|
275
|
+
notice_language: "EN",
|
|
276
|
+
notice_privacy_policy_url: "https://partner.test/privacy",
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
});
|
|
280
|
+
const tokenPayload = decodeSmileIDToken(token)?.tokenPayload;
|
|
281
|
+
expect(tokenPayload?.hasGivenNames).toBe(true);
|
|
282
|
+
expect(tokenPayload?.hasLastName).toBe(true);
|
|
283
|
+
expect(tokenPayload?.hasEmail).toBe(true);
|
|
284
|
+
expect(tokenPayload?.hasPhoneNumber).toBe(true);
|
|
285
|
+
expect(isTokenConsentComplete(tokenPayload?.consent)).toBe(true);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("parses a subset claim — exact flags plus a partial (incomplete) consent", () => {
|
|
289
|
+
const token = makeJwt({
|
|
290
|
+
payload: {
|
|
291
|
+
email: "pii_ghi",
|
|
292
|
+
consent: {
|
|
293
|
+
granted: true,
|
|
294
|
+
granted_at: "2026-01-01T09:00:00.000Z",
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
const tokenPayload = decodeSmileIDToken(token)?.tokenPayload;
|
|
299
|
+
expect(tokenPayload?.hasGivenNames).toBe(false);
|
|
300
|
+
expect(tokenPayload?.hasLastName).toBe(false);
|
|
301
|
+
expect(tokenPayload?.hasEmail).toBe(true);
|
|
302
|
+
expect(tokenPayload?.hasPhoneNumber).toBe(false);
|
|
303
|
+
expect(tokenPayload?.consent).toEqual({
|
|
304
|
+
granted: true,
|
|
305
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
306
|
+
});
|
|
307
|
+
expect(isTokenConsentComplete(tokenPayload?.consent)).toBe(false);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("leaves tokenPayload undefined when the payload claim is absent", () => {
|
|
311
|
+
const token = makeJwt({ sub: "partner-1", iat: 1_700_000_000 });
|
|
312
|
+
const result = decodeSmileIDToken(token);
|
|
313
|
+
expect(result).toBeDefined();
|
|
314
|
+
expect(result?.tokenPayload).toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("leaves tokenPayload undefined when the payload claim is not an object", () => {
|
|
318
|
+
for (const claim of ["a-string", ["an", "array"], 42, null]) {
|
|
319
|
+
const result = decodeSmileIDToken(makeJwt({ payload: claim }));
|
|
320
|
+
expect(result).toBeDefined();
|
|
321
|
+
expect(result?.tokenPayload).toBeUndefined();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("tolerates malformed consent and unknown keys inside the claim", () => {
|
|
326
|
+
const stringConsent = decodeSmileIDToken(
|
|
327
|
+
makeJwt({ payload: { consent: "granted", last_name: "pii_x" } }),
|
|
328
|
+
)?.tokenPayload;
|
|
329
|
+
expect(stringConsent?.consent).toBeUndefined();
|
|
330
|
+
expect(stringConsent?.hasLastName).toBe(true);
|
|
331
|
+
|
|
332
|
+
const arrayConsent = decodeSmileIDToken(
|
|
333
|
+
makeJwt({ payload: { consent: [true] } }),
|
|
334
|
+
)?.tokenPayload;
|
|
335
|
+
expect(arrayConsent?.consent).toBeUndefined();
|
|
336
|
+
|
|
337
|
+
const emptyConsent = decodeSmileIDToken(
|
|
338
|
+
makeJwt({ payload: { consent: {} } }),
|
|
339
|
+
)?.tokenPayload;
|
|
340
|
+
expect(emptyConsent?.consent).toBeUndefined();
|
|
341
|
+
|
|
342
|
+
const untrustedGranted = decodeSmileIDToken(
|
|
343
|
+
makeJwt({
|
|
344
|
+
payload: {
|
|
345
|
+
consent: { granted: "yes", notice_language: "EN" },
|
|
346
|
+
unknown_key: "ignored",
|
|
347
|
+
},
|
|
348
|
+
}),
|
|
349
|
+
)?.tokenPayload;
|
|
350
|
+
expect(untrustedGranted?.consent?.granted).toBeUndefined();
|
|
351
|
+
expect(untrustedGranted?.consent?.noticeLanguage).toBe("EN");
|
|
352
|
+
expect(isTokenConsentComplete(untrustedGranted?.consent)).toBe(false);
|
|
353
|
+
|
|
354
|
+
const deniedGranted = decodeSmileIDToken(
|
|
355
|
+
makeJwt({ payload: { consent: { granted: false } } }),
|
|
356
|
+
)?.tokenPayload;
|
|
357
|
+
expect(deniedGranted?.consent).toBeUndefined();
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("regression: a legacy token (sdk_config only) still parses sdkConfig with tokenPayload unset", () => {
|
|
361
|
+
const token = makeJwt({
|
|
362
|
+
partner_id: "002",
|
|
363
|
+
sdk_config: { selfie: { width: 640, height: 480, quality: 90 } },
|
|
364
|
+
});
|
|
365
|
+
const result = decodeSmileIDToken(token);
|
|
366
|
+
expect(result?.sdkConfig?.selfie?.width).toBe(640);
|
|
367
|
+
expect(result?.partnerId).toBe("002");
|
|
368
|
+
expect(result?.tokenPayload).toBeUndefined();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it("parses payload and sdk_config claims side by side", () => {
|
|
372
|
+
const token = makeJwt({
|
|
373
|
+
sdk_config: { livenessImageCount: 5 },
|
|
374
|
+
payload: { given_names: "pii_abc" },
|
|
375
|
+
});
|
|
376
|
+
const result = decodeSmileIDToken(token);
|
|
377
|
+
expect(result?.sdkConfig?.livenessImageCount).toBe(5);
|
|
378
|
+
expect(result?.tokenPayload?.hasGivenNames).toBe(true);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("decodes a minted token that binds names and email but not phone_number", () => {
|
|
382
|
+
// Same deployment, partial binding: given_names, last_name and email
|
|
383
|
+
// only. The contact requirement is satisfied by the bound email alone.
|
|
384
|
+
const mintedToken =
|
|
385
|
+
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
|
|
386
|
+
"eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiZW1haWwiOiJwaWlfMDFrcDNzOXQ3OGZleDkxcWg0NnlwODdyanEiLCJnaXZlbl9uYW1lcyI6InBpaV8wMWt6cm0xNzB0ZjY5cnE5NTM4cnpnZXR5eiIsImxhc3RfbmFtZSI6InBpaV8wMWtwM3M5dDdyZThocjZkZXI5cTNucHo3YyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDU5ODcxLCJpYXQiOjE3ODY0NTg5NzEsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
|
|
387
|
+
"s5BFthU2B1aKxYDYyyWYDmRpLoHibH34r2Lk4OS-wZQC8pmYROjWBiORx6n2_0srmxz_SA2eZo_FHyAr-ew_Zg";
|
|
388
|
+
|
|
389
|
+
const tokenPayload = decodeSmileIDToken(mintedToken)?.tokenPayload;
|
|
390
|
+
|
|
391
|
+
expect(tokenPayload?.hasGivenNames).toBe(true);
|
|
392
|
+
expect(tokenPayload?.hasLastName).toBe(true);
|
|
393
|
+
expect(tokenPayload?.hasEmail).toBe(true);
|
|
394
|
+
expect(tokenPayload?.hasPhoneNumber).toBe(false);
|
|
395
|
+
expect(bindsRequiredUserDetails(tokenPayload)).toBe(true);
|
|
396
|
+
expect(isTokenConsentComplete(tokenPayload?.consent)).toBe(true);
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it("decodes the payload claim from a token minted by the v3 token endpoint", () => {
|
|
400
|
+
// Captured from the /v3/token deployment: ES256-signed, extra top-level
|
|
401
|
+
// claims (api_url, key_id, aud, iss), every user-detail field bound as an
|
|
402
|
+
// opaque pii_ reference, a complete consent object, and in-claim identity
|
|
403
|
+
// fields (country, id_type, id_number, callback_url) the SDK deliberately
|
|
404
|
+
// ignores. The token expired minutes after minting and carries no raw
|
|
405
|
+
// PII — the decoder never checks exp (the server enforces token
|
|
406
|
+
// validity).
|
|
407
|
+
const mintedToken =
|
|
408
|
+
"eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
|
|
409
|
+
"eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiY291bnRyeSI6Ik5HIiwiaWRfdHlwZSI6Ik5JTl9WMiIsImVtYWlsIjoicGlpXzAxa3Azczl0NzhmZXg5MXFoNDZ5cDg3cmpxIiwiZ2l2ZW5fbmFtZXMiOiJwaWlfMDFrenJtMTcwdGY2OXJxOTUzOHJ6Z2V0eXoiLCJpZF9udW1iZXIiOiJwaWlfMDFra2dhYTliNWZ2Z2EyMnllZXM1ZjRyNnQiLCJsYXN0X25hbWUiOiJwaWlfMDFrcDNzOXQ3cmU4aHI2ZGVyOXEzbnB6N2MiLCJwaG9uZV9udW1iZXIiOiJwaWlfMDFrenJtdHdkeGZlenJ0dGRmOXQzc3Rzem4iLCJjYWxsYmFja191cmwiOiJjYWxsYmFja18wMWt6cm10d2VmZTV5YWgzbmR2NW03MDc3ZyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDYwNzEyLCJpYXQiOjE3ODY0NTk4MTIsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
|
|
410
|
+
"cuZgo64eEt5vNvND_RvCVpJyTv4rqax9ebkLV4i4uI4raz6fBhazxspbaoErYC0wxwRmn5EhPAdAdDnNKI6tEQ";
|
|
411
|
+
|
|
412
|
+
const result = decodeSmileIDToken(mintedToken);
|
|
413
|
+
|
|
414
|
+
expect(result?.partnerId).toBe("1483");
|
|
415
|
+
expect(result?.tokenPayload).toEqual({
|
|
416
|
+
hasGivenNames: true,
|
|
417
|
+
hasLastName: true,
|
|
418
|
+
hasEmail: true,
|
|
419
|
+
hasPhoneNumber: true,
|
|
420
|
+
consent: {
|
|
421
|
+
granted: true,
|
|
422
|
+
grantedAt: "2026-01-01T09:00:00.000Z",
|
|
423
|
+
noticeLanguage: "EN",
|
|
424
|
+
noticePrivacyPolicyUrl: "https://juma.privacy-policy.com",
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
expect(bindsRequiredUserDetails(result?.tokenPayload)).toBe(true);
|
|
428
|
+
expect(isTokenConsentComplete(result?.tokenPayload?.consent)).toBe(true);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { isAxiosError } from "axios";
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
RetryStrategy,
|
|
4
|
+
type RetryConfig,
|
|
5
|
+
} from "../../core/models/network_config";
|
|
3
6
|
|
|
4
7
|
/**
|
|
5
8
|
* Retry helper. Retries `block` until success or `maxAttempts`
|
|
@@ -15,6 +18,11 @@ export async function withRetry<T>(
|
|
|
15
18
|
config: RetryConfig,
|
|
16
19
|
block: (attempt: number) => Promise<T>,
|
|
17
20
|
): Promise<T> {
|
|
21
|
+
// Without this the loop below never runs and rejects with `undefined`.
|
|
22
|
+
if (config.strategy === RetryStrategy.none || config.maxAttempts <= 1) {
|
|
23
|
+
return block(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
let lastError: unknown;
|
|
19
27
|
for (let attempt = 0; attempt < config.maxAttempts; attempt++) {
|
|
20
28
|
try {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ZodError } from "zod";
|
|
2
2
|
|
|
3
3
|
import { type SdkConfig, sdkConfigSchema } from "../model/sdk_config";
|
|
4
|
+
import { type TokenPayload, parseTokenPayload } from "../model/token_payload";
|
|
4
5
|
|
|
5
6
|
// `atob` is a global available in React Native's Hermes runtime and in
|
|
6
7
|
// modern browsers. The `lib: ["ES2022"]` tsconfig omits DOM types, so the
|
|
@@ -28,20 +29,31 @@ export type DecodedToken = {
|
|
|
28
29
|
* `undefined` when the claim is absent.
|
|
29
30
|
*/
|
|
30
31
|
readonly partnerId?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Token-bound user-detail presence flags and consent subfields from the
|
|
34
|
+
* top-level `payload` claim (a sibling of `sdk_config`). `undefined` when
|
|
35
|
+
* the claim is absent or not an object — legacy tokens keep today's strict
|
|
36
|
+
* validation. Same graceful degradation as `sdk_config`: a malformed claim
|
|
37
|
+
* never rejects the token.
|
|
38
|
+
*/
|
|
39
|
+
readonly tokenPayload?: TokenPayload;
|
|
31
40
|
};
|
|
32
41
|
|
|
33
42
|
/**
|
|
34
|
-
* Decode a Smile ID JWT auth token and extract the `sdk_config`
|
|
43
|
+
* Decode a Smile ID JWT auth token and extract the `sdk_config` and
|
|
44
|
+
* `payload` claims.
|
|
35
45
|
*
|
|
36
46
|
* The JWT signature is intentionally NOT verified — the server validates
|
|
37
|
-
* authenticity; the SDK only reads capture settings
|
|
47
|
+
* authenticity; the SDK only reads capture settings and token-bound
|
|
48
|
+
* user-detail/consent presence from the payload.
|
|
38
49
|
*
|
|
39
50
|
* Return semantics:
|
|
40
51
|
* - Structurally invalid token (not exactly three `.`-delimited
|
|
41
52
|
* segments, non-base64url payload, non-object JSON) → `undefined`.
|
|
42
53
|
* - Valid token with no `sdk_config` claim, a non-object claim, or a
|
|
43
54
|
* claim that fails Zod parsing → non-`undefined` `DecodedToken`
|
|
44
|
-
* with `sdkConfig` unset.
|
|
55
|
+
* with `sdkConfig` unset. The `payload` claim degrades the same
|
|
56
|
+
* way into `tokenPayload`.
|
|
45
57
|
* - Valid token with a parseable `sdk_config` → non-`undefined`
|
|
46
58
|
* `DecodedToken` with `sdkConfig` set.
|
|
47
59
|
*
|
|
@@ -58,16 +70,21 @@ export function decodeSmileIDToken(token: string): DecodedToken | undefined {
|
|
|
58
70
|
const partnerIdField =
|
|
59
71
|
typeof rawPartnerId === "string" ? { partnerId: rawPartnerId } : {};
|
|
60
72
|
|
|
73
|
+
// Token-bound user details & consent — a TOP-LEVEL `payload` claim, another
|
|
74
|
+
// sibling of `sdk_config`. Absent/malformed degrades to unset, never rejects.
|
|
75
|
+
const tokenPayload = parseTokenPayload(payloadObject["payload"]);
|
|
76
|
+
const tokenPayloadField = tokenPayload !== undefined ? { tokenPayload } : {};
|
|
77
|
+
|
|
61
78
|
const rawClaim = payloadObject["sdk_config"];
|
|
62
79
|
|
|
63
80
|
if (rawClaim === undefined || rawClaim === null) {
|
|
64
81
|
// No claim present — defaults apply throughout the flow.
|
|
65
|
-
return { ...partnerIdField };
|
|
82
|
+
return { ...partnerIdField, ...tokenPayloadField };
|
|
66
83
|
}
|
|
67
84
|
|
|
68
85
|
if (typeof rawClaim !== "object" || Array.isArray(rawClaim)) {
|
|
69
86
|
// Claim exists but is not an object — treat as absent.
|
|
70
|
-
return { ...partnerIdField };
|
|
87
|
+
return { ...partnerIdField, ...tokenPayloadField };
|
|
71
88
|
}
|
|
72
89
|
|
|
73
90
|
const parseResult = sdkConfigSchema.safeParse(rawClaim);
|
|
@@ -77,12 +94,16 @@ export function decodeSmileIDToken(token: string): DecodedToken | undefined {
|
|
|
77
94
|
// Unreachable in practice (safeParse always gives ZodError on
|
|
78
95
|
// failure), but degrade gracefully to preserve the never-throws
|
|
79
96
|
// contract rather than surfacing an unexpected error type.
|
|
80
|
-
return { ...partnerIdField };
|
|
97
|
+
return { ...partnerIdField, ...tokenPayloadField };
|
|
81
98
|
}
|
|
82
|
-
return { ...partnerIdField };
|
|
99
|
+
return { ...partnerIdField, ...tokenPayloadField };
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
return {
|
|
102
|
+
return {
|
|
103
|
+
sdkConfig: parseResult.data,
|
|
104
|
+
...partnerIdField,
|
|
105
|
+
...tokenPayloadField,
|
|
106
|
+
};
|
|
86
107
|
}
|
|
87
108
|
|
|
88
109
|
/**
|
package/src/index.ts
CHANGED
|
@@ -27,6 +27,7 @@ export {
|
|
|
27
27
|
// subclass holds a stable `errorCode` and `severity` so partner
|
|
28
28
|
// tooling can pivot on either without parsing the message text.
|
|
29
29
|
export {
|
|
30
|
+
AgentModeWithEnhancedLivenessException,
|
|
30
31
|
BuilderValidationException,
|
|
31
32
|
ConsentDeniedException,
|
|
32
33
|
DuplicateScreenTypeException,
|
|
@@ -225,6 +226,11 @@ export {
|
|
|
225
226
|
type UseSmileIDResult,
|
|
226
227
|
} from "./core/models/result";
|
|
227
228
|
export type { ConfigOptions } from "./core/models/config";
|
|
229
|
+
export {
|
|
230
|
+
GlobalPrivacyControl,
|
|
231
|
+
GLOBAL_PRIVACY_CONTROL_WIRE_KEY,
|
|
232
|
+
globalPrivacyControlFromWire,
|
|
233
|
+
} from "./core/models/global_privacy_control";
|
|
228
234
|
export { LogLevel } from "./core/models/network_config";
|
|
229
235
|
export type {
|
|
230
236
|
CacheConfig,
|
|
@@ -374,10 +380,6 @@ export {
|
|
|
374
380
|
UseSmileIDDocumentOverlay,
|
|
375
381
|
type UseSmileIDDocumentOverlayProps,
|
|
376
382
|
} from "./presentation/components/use_smile_id_document_overlay";
|
|
377
|
-
export {
|
|
378
|
-
UseSmileIDFaceSilhouetteOverlay,
|
|
379
|
-
type UseSmileIDFaceSilhouetteOverlayProps,
|
|
380
|
-
} from "./presentation/components/use_smile_id_face_silhouette_overlay";
|
|
381
383
|
export {
|
|
382
384
|
UseSmileIDInstructionsHero,
|
|
383
385
|
type UseSmileIDInstructionsHeroProps,
|