@smileid/usesmileid 12.0.0 → 12.0.1
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 +4 -0
- 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/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/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 +1 -1
- package/dist/data/api/smile_id_api.d.ts.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/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/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/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_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 +3 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
- package/dist/presentation/flow/dsl/builder/screens_builder.js +3 -6
- package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts +5 -1
- package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
- package/dist/presentation/flow/flow_navigation_manager.js +7 -1
- 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/use_smile_id_flow.d.ts.map +1 -1
- package/dist/presentation/flow/use_smile_id_flow.js +10 -4
- package/dist/presentation/flow/use_smile_id_flow.js.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.d.ts.map +1 -1
- package/dist/presentation/flow/validation/flow_validator.js +42 -20
- package/dist/presentation/flow/validation/flow_validator.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 +36 -15
- 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__/setup_react_native.ts +17 -2
- package/src/__tests__/snapshots/__snapshots__/screens_structural.test.tsx.snap +5261 -0
- package/src/__tests__/snapshots/screens_structural.test.tsx +230 -0
- package/src/camera/__tests__/use_smile_id_camera_system.test.ts +103 -0
- package/src/core/exception/__tests__/builder_validation_exceptions.test.ts +67 -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/index.ts +1 -0
- 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 +1 -1
- 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/compression/__tests__/payload_compressor_real_jpeg.test.ts +6 -2
- 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/repository/__tests__/network_retries.test.ts +1 -1
- 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/smile_id_urls.ts +1 -1
- package/src/data/util/__tests__/retry_util.test.ts +25 -0
- package/src/data/util/retry_util.ts +9 -1
- package/src/index.ts +6 -4
- 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__/use_smile_id_metadata.test.ts +1 -1
- 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_enhanced_smart_selfie_clip.test.tsx +9 -4
- package/src/presentation/components/__tests__/use_smile_id_instructions_hero.test.tsx +44 -0
- 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 +49 -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 +3 -6
- package/src/presentation/flow/flow_navigation_manager.ts +6 -1
- package/src/presentation/flow/model/instructions_defaults.ts +1 -1
- 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/use_smile_id_flow.tsx +11 -4
- package/src/presentation/flow/validation/__tests__/flow_validator.test.ts +61 -1
- package/src/presentation/flow/validation/flow_validator.ts +55 -26
- 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__/scan_state_to_overlay_state.test.ts +47 -27
- package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +176 -14
- package/src/presentation/screen/__tests__/use_smile_id_document_back_instructions.test.tsx +1 -1
- 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_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 +47 -19
- 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/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
package/src/presentation/components/__tests__/use_smile_id_enhanced_smart_selfie_clip.test.tsx
CHANGED
|
@@ -8,8 +8,9 @@ import {
|
|
|
8
8
|
import { AccessibilityInfo } from "react-native";
|
|
9
9
|
import {
|
|
10
10
|
UseSmileIDEnhancedSmartSelfieClip,
|
|
11
|
-
|
|
11
|
+
ENHANCED_SMART_SELFIE_ARROW_FIT,
|
|
12
12
|
} from "../use_smile_id_enhanced_smart_selfie_clip";
|
|
13
|
+
import { FaceOvalDefaults } from "../face_oval_defaults";
|
|
13
14
|
import {
|
|
14
15
|
ENHANCED_SMART_SELFIE_ANIMATION_SPEED,
|
|
15
16
|
ENHANCED_SMART_SELFIE_TOTAL_FRAMES,
|
|
@@ -195,7 +196,7 @@ describe("UseSmileIDEnhancedSmartSelfieClip", () => {
|
|
|
195
196
|
});
|
|
196
197
|
|
|
197
198
|
describe("positioning", () => {
|
|
198
|
-
it("
|
|
199
|
+
it("sizes the container to the oval width times the arrow-fit factor", async () => {
|
|
199
200
|
const tree = renderClip({ pingPong: false });
|
|
200
201
|
await act(async () => {
|
|
201
202
|
await Promise.resolve();
|
|
@@ -209,8 +210,12 @@ describe("UseSmileIDEnhancedSmartSelfieClip", () => {
|
|
|
209
210
|
);
|
|
210
211
|
expect(container).toBeDefined();
|
|
211
212
|
const style = container!.props.style as { width: number; height: number };
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
const expectedSize =
|
|
214
|
+
BASE_CONTAINER_WIDTH *
|
|
215
|
+
FaceOvalDefaults.ovalWidthRatio *
|
|
216
|
+
ENHANCED_SMART_SELFIE_ARROW_FIT;
|
|
217
|
+
expect(style.width).toBe(expectedSize);
|
|
218
|
+
expect(style.height).toBe(expectedSize);
|
|
214
219
|
});
|
|
215
220
|
});
|
|
216
221
|
|
|
@@ -78,6 +78,50 @@ describe("UseSmileIDInstructionsHero", () => {
|
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
it("pins oval-less heroes to the software Lottie engine on iOS", () => {
|
|
82
|
+
// lottie-ios's CA engine mis-renders the doc heroes' track mattes, so
|
|
83
|
+
// oval-less heroes must pin renderMode SOFTWARE on iOS.
|
|
84
|
+
const docTree = render(
|
|
85
|
+
<UseSmileIDInstructionsHero
|
|
86
|
+
source={SELFIE_HERO_ANIMATION}
|
|
87
|
+
showOval={false}
|
|
88
|
+
showOvalProgress={false}
|
|
89
|
+
/>,
|
|
90
|
+
);
|
|
91
|
+
const docHero = docTree.root.findByProps({
|
|
92
|
+
testID: "si_instructions_hero",
|
|
93
|
+
});
|
|
94
|
+
act(() => {
|
|
95
|
+
docHero.props.onLayout({
|
|
96
|
+
nativeEvent: { layout: { width: 390, height: 600 } },
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
expect(docTree.root.findByType(AnimatedLottieView).props.renderMode).toBe(
|
|
100
|
+
"SOFTWARE",
|
|
101
|
+
);
|
|
102
|
+
act(() => docTree.unmount());
|
|
103
|
+
|
|
104
|
+
const ovalTree = render(
|
|
105
|
+
<UseSmileIDInstructionsHero
|
|
106
|
+
source={SELFIE_HERO_ANIMATION}
|
|
107
|
+
showOval
|
|
108
|
+
showOvalProgress={false}
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
111
|
+
const ovalHero = ovalTree.root.findByProps({
|
|
112
|
+
testID: "si_instructions_hero",
|
|
113
|
+
});
|
|
114
|
+
act(() => {
|
|
115
|
+
ovalHero.props.onLayout({
|
|
116
|
+
nativeEvent: { layout: { width: 390, height: 600 } },
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
expect(
|
|
120
|
+
ovalTree.root.findByType(AnimatedLottieView).props.renderMode,
|
|
121
|
+
).toBeUndefined();
|
|
122
|
+
act(() => ovalTree.unmount());
|
|
123
|
+
});
|
|
124
|
+
|
|
81
125
|
it("gives the document card hero explicit dimensions (no intrinsic-size flash)", () => {
|
|
82
126
|
const tree = render(
|
|
83
127
|
<UseSmileIDInstructionsHero
|
|
@@ -13,8 +13,11 @@ import {
|
|
|
13
13
|
export const ESS_ALL_DIRECTIONS_ANIMATION =
|
|
14
14
|
require("../assets/si_ess_animation_all_directions.json") as AnimationObject;
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Box width as a multiple of the oval width; the baked arrow tips touch the
|
|
18
|
+
* oval boundary at ≈1.19× — keep this factor below that.
|
|
19
|
+
*/
|
|
20
|
+
export const ENHANCED_SMART_SELFIE_ARROW_FIT = 1.15;
|
|
18
21
|
/** Nudge so the composition's face lands on the oval centre. */
|
|
19
22
|
export const VERTICAL_ALIGNMENT_OFFSET = -15;
|
|
20
23
|
|
|
@@ -46,7 +49,10 @@ export function UseSmileIDEnhancedSmartSelfieClip(
|
|
|
46
49
|
return null;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const size =
|
|
52
|
+
const size =
|
|
53
|
+
containerWidth *
|
|
54
|
+
FaceOvalDefaults.ovalWidthRatio *
|
|
55
|
+
ENHANCED_SMART_SELFIE_ARROW_FIT;
|
|
50
56
|
const ovalCenterY = containerHeight * FaceOvalDefaults.ovalCenterYRatio;
|
|
51
57
|
|
|
52
58
|
const containerStyle = {
|
|
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import {
|
|
3
3
|
Animated,
|
|
4
4
|
Easing,
|
|
5
|
+
Platform,
|
|
5
6
|
StyleSheet,
|
|
6
7
|
View,
|
|
7
8
|
type LayoutChangeEvent,
|
|
@@ -160,6 +161,13 @@ export function UseSmileIDInstructionsHero({
|
|
|
160
161
|
// motion is reduced; otherwise let it play on its own clock (autoPlay+loop).
|
|
161
162
|
const lottieDrivenByProgress = showOvalProgress || reducedMotion;
|
|
162
163
|
|
|
164
|
+
// lottie-ios's Core Animation engine mis-renders the document heroes' track mattes,
|
|
165
|
+
// so oval-less heroes use the main-thread engine on iOS; Android keeps its default.
|
|
166
|
+
const renderModeProps =
|
|
167
|
+
Platform.OS === "ios" && !showOval
|
|
168
|
+
? ({ renderMode: "SOFTWARE" } as const)
|
|
169
|
+
: {};
|
|
170
|
+
|
|
163
171
|
// Inset the ring by half the stroke so it isn't clipped at the SVG edge.
|
|
164
172
|
const ellipseProps = {
|
|
165
173
|
cx: ovalWidth / 2,
|
|
@@ -196,6 +204,7 @@ export function UseSmileIDInstructionsHero({
|
|
|
196
204
|
autoPlay={!lottieDrivenByProgress}
|
|
197
205
|
loop={!lottieDrivenByProgress}
|
|
198
206
|
{...(lottieDrivenByProgress ? { progress: heroProgress } : {})}
|
|
207
|
+
{...renderModeProps}
|
|
199
208
|
resizeMode="contain"
|
|
200
209
|
style={{ width: cardWidth, height: cardHeight }}
|
|
201
210
|
/>
|
|
@@ -266,6 +275,7 @@ export function UseSmileIDInstructionsHero({
|
|
|
266
275
|
autoPlay={!lottieDrivenByProgress}
|
|
267
276
|
loop={!lottieDrivenByProgress}
|
|
268
277
|
{...(lottieDrivenByProgress ? { progress: heroProgress } : {})}
|
|
278
|
+
{...renderModeProps}
|
|
269
279
|
style={{
|
|
270
280
|
position: "absolute",
|
|
271
281
|
width: animationSize,
|
|
@@ -74,15 +74,15 @@ describe("FlowNavigationManager — document per-side preview loop", () => {
|
|
|
74
74
|
true,
|
|
75
75
|
);
|
|
76
76
|
|
|
77
|
-
const
|
|
77
|
+
const greenBookFlow = buildScreens((screens) => {
|
|
78
78
|
screens.capture((capture) => {
|
|
79
79
|
capture.captureType = CaptureType.document;
|
|
80
80
|
capture.document((document) => {
|
|
81
|
-
document.documentType = DocumentType.
|
|
81
|
+
document.documentType = DocumentType.SouthAfricaGreenBook; // no back side
|
|
82
82
|
});
|
|
83
83
|
});
|
|
84
84
|
});
|
|
85
|
-
expect(makeManager(
|
|
85
|
+
expect(makeManager(greenBookFlow).documentCapturesBack()).toBe(false);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it("after the FRONT preview, routes back to the document capture for the BACK", () => {
|
|
@@ -602,6 +602,55 @@ describe("FlowNavigationManager — submission lifecycle", () => {
|
|
|
602
602
|
expect(manager.jobSubmissionState.kind).toBe("success");
|
|
603
603
|
});
|
|
604
604
|
|
|
605
|
+
it("retrySubmission double-trigger fires exactly one new submission", async () => {
|
|
606
|
+
// Concrete scenario: the processing screen's retry button is
|
|
607
|
+
// double-tapped after a failed submission. The second tap must not
|
|
608
|
+
// reset the in-flight `loading` latch and launch a duplicate job.
|
|
609
|
+
const repository = new StubRepository();
|
|
610
|
+
repository.result = failure(new Error("transient"));
|
|
611
|
+
const manager = makeManager(
|
|
612
|
+
buildScreens((screens) => screens.processing()),
|
|
613
|
+
vi.fn(),
|
|
614
|
+
{
|
|
615
|
+
enhancedKYCParams: {
|
|
616
|
+
idType: "BVN",
|
|
617
|
+
idNumber: "1",
|
|
618
|
+
country: "NG",
|
|
619
|
+
},
|
|
620
|
+
userDetails: SAMPLE_USER_DETAILS,
|
|
621
|
+
network: {
|
|
622
|
+
config: {
|
|
623
|
+
jobType: JobType.enhancedKyc,
|
|
624
|
+
token: "k",
|
|
625
|
+
partnerConfig: {
|
|
626
|
+
partnerId: "p",
|
|
627
|
+
callbackUrl: "",
|
|
628
|
+
useSandbox: true,
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
},
|
|
632
|
+
},
|
|
633
|
+
repository,
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
// Initial attempt fails, leaving the retry affordance visible.
|
|
637
|
+
manager.beginSubmissionIfNeeded();
|
|
638
|
+
await flushMicrotasks();
|
|
639
|
+
expect(manager.jobSubmissionState.kind).toBe("failure");
|
|
640
|
+
expect(repository.received).toHaveLength(1);
|
|
641
|
+
|
|
642
|
+
// The retried submission stays in flight so both taps land while
|
|
643
|
+
// the request is loading.
|
|
644
|
+
repository.manual = true;
|
|
645
|
+
manager.retrySubmission();
|
|
646
|
+
manager.retrySubmission(); // double-tap — ignored while in flight
|
|
647
|
+
|
|
648
|
+
await flushMicrotasks();
|
|
649
|
+
expect(manager.jobSubmissionState.kind).toBe("loading");
|
|
650
|
+
// Exactly one new submission: the initial failure plus one retry.
|
|
651
|
+
expect(repository.received).toHaveLength(2);
|
|
652
|
+
});
|
|
653
|
+
|
|
605
654
|
it("does not update state if the manager is disposed mid-submission", async () => {
|
|
606
655
|
const repository = new StubRepository();
|
|
607
656
|
repository.manual = true;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import * as ScreenOrientation from "expo-screen-orientation";
|
|
3
|
+
import { GlobalPrivacyControl } from "../../../../../core/models/global_privacy_control";
|
|
4
|
+
import { UseSmileIDConfigBuilder } from "../config_builder";
|
|
5
|
+
|
|
6
|
+
describe("UseSmileIDConfigBuilder", () => {
|
|
7
|
+
it("snapshots the documented defaults", () => {
|
|
8
|
+
expect(new UseSmileIDConfigBuilder().build()).toEqual({
|
|
9
|
+
enableCrashReporting: true,
|
|
10
|
+
enableDebugMode: false,
|
|
11
|
+
allowOfflineMode: false,
|
|
12
|
+
globalPrivacyControl: GlobalPrivacyControl.OptOut,
|
|
13
|
+
exitOrientations: ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("carries an overridden exit orientation lock into the snapshot", () => {
|
|
18
|
+
const builder = new UseSmileIDConfigBuilder();
|
|
19
|
+
builder.exitOrientations = ScreenOrientation.OrientationLock.DEFAULT;
|
|
20
|
+
|
|
21
|
+
expect(builder.build().exitOrientations).toBe(
|
|
22
|
+
ScreenOrientation.OrientationLock.DEFAULT,
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import * as ScreenOrientation from "expo-screen-orientation";
|
|
1
2
|
import type { ConfigOptions } from "../../../../core/models/config";
|
|
3
|
+
import { GlobalPrivacyControl } from "../../../../core/models/global_privacy_control";
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* `b.config((c) => { c.enableCrashReporting = false; ... })`
|
|
@@ -26,11 +28,30 @@ export class UseSmileIDConfigBuilder {
|
|
|
26
28
|
*/
|
|
27
29
|
allowOfflineMode = false;
|
|
28
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Consent posture for every session (`global_privacy_control`). Defaults
|
|
33
|
+
* to `GlobalPrivacyControl.OptOut` — the opt-out consent layout is
|
|
34
|
+
* enforced universally and regional detection is ignored. Set
|
|
35
|
+
* `GlobalPrivacyControl.OptIn` to keep the legacy regional mechanics
|
|
36
|
+
* (opt-in inside the EU, opt-out elsewhere).
|
|
37
|
+
*/
|
|
38
|
+
globalPrivacyControl: GlobalPrivacyControl = GlobalPrivacyControl.OptOut;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Orientation lock applied when a capture screen leaves the screen.
|
|
42
|
+
* Defaults to portrait-up; set `ScreenOrientation.OrientationLock.DEFAULT`
|
|
43
|
+
* if your app supports more than one orientation.
|
|
44
|
+
*/
|
|
45
|
+
exitOrientations: ScreenOrientation.OrientationLock =
|
|
46
|
+
ScreenOrientation.OrientationLock.PORTRAIT_UP;
|
|
47
|
+
|
|
29
48
|
build(): ConfigOptions {
|
|
30
49
|
return Object.freeze({
|
|
31
50
|
enableCrashReporting: this.enableCrashReporting,
|
|
32
51
|
enableDebugMode: this.enableDebugMode,
|
|
33
52
|
allowOfflineMode: this.allowOfflineMode,
|
|
53
|
+
globalPrivacyControl: this.globalPrivacyControl,
|
|
54
|
+
exitOrientations: this.exitOrientations,
|
|
34
55
|
});
|
|
35
56
|
}
|
|
36
57
|
}
|
|
@@ -89,8 +89,9 @@ export class InstructionsConfigBuilder {
|
|
|
89
89
|
|
|
90
90
|
export class SelfieCaptureConfigBuilder {
|
|
91
91
|
/**
|
|
92
|
-
* When true,
|
|
93
|
-
* the subject.
|
|
92
|
+
* When true, the whole selfie session captures on the rear camera so an
|
|
93
|
+
* assisting agent can point it at the subject. Cannot be combined with
|
|
94
|
+
* {@link enableEnhancedLiveness}.
|
|
94
95
|
*/
|
|
95
96
|
allowAgentMode = false;
|
|
96
97
|
/**
|
|
@@ -180,10 +181,6 @@ export class PreviewConfigBuilder {
|
|
|
180
181
|
* true.
|
|
181
182
|
*/
|
|
182
183
|
showAttribution = true;
|
|
183
|
-
/** Provides a custom continue button in place of the default. */
|
|
184
|
-
continueButton?: ButtonSlot;
|
|
185
|
-
/** Provides a custom cancel button in place of the default. */
|
|
186
|
-
cancelButton?: ButtonSlot;
|
|
187
184
|
}
|
|
188
185
|
|
|
189
186
|
export class ProcessingConfigBuilder {
|
|
@@ -693,9 +693,14 @@ export class FlowNavigationManager {
|
|
|
693
693
|
void this._submit();
|
|
694
694
|
}
|
|
695
695
|
|
|
696
|
-
/**
|
|
696
|
+
/**
|
|
697
|
+
* Reset to `notAttempted` and re-fire submission (processing screen's
|
|
698
|
+
* onRetry). Ignored while a submission is in flight — resetting then would
|
|
699
|
+
* defeat `beginSubmissionIfNeeded()`'s single-flight guard.
|
|
700
|
+
*/
|
|
697
701
|
retrySubmission(): void {
|
|
698
702
|
if (this._disposed) return;
|
|
703
|
+
if (this._jobSubmissionState.kind === "loading") return;
|
|
699
704
|
this._setSubmissionState({ kind: "notAttempted" });
|
|
700
705
|
this.beginSubmissionIfNeeded();
|
|
701
706
|
}
|
|
@@ -31,7 +31,7 @@ export const SELFIE_HERO_ANIMATION =
|
|
|
31
31
|
require("../../assets/si_selfie_hero_animation.json") as AnimationObject;
|
|
32
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
33
33
|
export const ESS_HERO_ANIMATION =
|
|
34
|
-
require("../../assets/
|
|
34
|
+
require("../../assets/si_ess_all_directions_hero_animation.json") as AnimationObject;
|
|
35
35
|
|
|
36
36
|
// Document hero animations — per-document-type front instruction heroes, plus
|
|
37
37
|
// the back-of-ID (flip) hero shown on the back-capture interstitial.
|
|
@@ -75,10 +75,15 @@ export function OrchestratedCaptureScreen({
|
|
|
75
75
|
// selfie to portrait. The flip interstitial is a portrait instruction screen,
|
|
76
76
|
// so it must NOT inherit the document's landscape lock — force portrait while
|
|
77
77
|
// it shows, then revert to the document orientation for the back capture.
|
|
78
|
+
const exitOrientations =
|
|
79
|
+
navigationManager.configuration.config?.exitOrientations;
|
|
78
80
|
useCaptureOrientationLock(
|
|
79
81
|
captureType === CaptureType.document && !showBackInstructions
|
|
80
82
|
? (documentType?.orientation ?? "Landscape")
|
|
81
83
|
: "Portrait",
|
|
84
|
+
{
|
|
85
|
+
...(exitOrientations !== undefined ? { exitLock: exitOrientations } : {}),
|
|
86
|
+
},
|
|
82
87
|
);
|
|
83
88
|
|
|
84
89
|
// Selfie also pushes the registry-built wire FaceConfig; documents have no
|
|
@@ -184,6 +189,7 @@ export function OrchestratedCaptureScreen({
|
|
|
184
189
|
enableEnhancedLiveness={
|
|
185
190
|
configuration.selfieConfig?.enableEnhancedLiveness ?? false
|
|
186
191
|
}
|
|
192
|
+
allowAgentMode={configuration.selfieConfig?.allowAgentMode ?? false}
|
|
187
193
|
{...(captureType === CaptureType.document && documentConfig
|
|
188
194
|
? {
|
|
189
195
|
// Type ratio → partner override → orientation default; matches
|
|
@@ -261,9 +267,18 @@ export function OrchestratedCaptureScreen({
|
|
|
261
267
|
...(fingerprint ? { fingerprint } : {}),
|
|
262
268
|
}),
|
|
263
269
|
)
|
|
264
|
-
.then(() =>
|
|
265
|
-
SmileExifTagger.finalize(primary, imageTypeForCapture)
|
|
266
|
-
|
|
270
|
+
.then(async () => {
|
|
271
|
+
await SmileExifTagger.finalize(primary, imageTypeForCapture);
|
|
272
|
+
// Liveness frames ship in the same submission, so they need the tag too.
|
|
273
|
+
await Promise.all(
|
|
274
|
+
livenessUrls.map((livenessUrl) =>
|
|
275
|
+
SmileExifTagger.finalize(livenessUrl, ImageType.Liveness),
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
})
|
|
279
|
+
// Tagging is fail-open: a rejection must not become an unhandled
|
|
280
|
+
// rejection, and navigation still runs in `finally`.
|
|
281
|
+
.catch(() => undefined)
|
|
267
282
|
.finally(async () => {
|
|
268
283
|
if (captureType === CaptureType.selfie) {
|
|
269
284
|
navigationManager.setCapturedSelfie(primary, livenessUrls);
|
|
@@ -2,6 +2,7 @@ import type { ConsentConfigBuilder } from "../dsl/builder/screens_builder";
|
|
|
2
2
|
import type { ConsentInformation } from "../../../core/models/params";
|
|
3
3
|
import { UseSmileIDCrashReporting } from "@smileid/usesmileid_bridge";
|
|
4
4
|
import { ConsentDeniedException } from "../../../core/exception";
|
|
5
|
+
import { GlobalPrivacyControl } from "../../../core/models/global_privacy_control";
|
|
5
6
|
import { UseSmileIDConsentScreen } from "../../screen/use_smile_id_consent_screen";
|
|
6
7
|
import { UseSmileIDOrchestratedScreenHost } from "./use_smile_id_orchestrated_screen_host";
|
|
7
8
|
|
|
@@ -25,6 +26,10 @@ export function OrchestratedConsentScreen({
|
|
|
25
26
|
content={({ navigationManager, onNavigateNext }) => (
|
|
26
27
|
<UseSmileIDConsentScreen
|
|
27
28
|
partnerName={configuration.partnerName}
|
|
29
|
+
globalPrivacyControl={
|
|
30
|
+
navigationManager.configuration.config?.globalPrivacyControl ??
|
|
31
|
+
GlobalPrivacyControl.OptOut
|
|
32
|
+
}
|
|
28
33
|
{...(configuration.partnerIcon !== undefined
|
|
29
34
|
? { partnerIcon: configuration.partnerIcon }
|
|
30
35
|
: {})}
|
|
@@ -50,18 +50,6 @@ export function OrchestratedPreviewScreen({
|
|
|
50
50
|
{...(navigationManager.captured.selfiePath !== undefined
|
|
51
51
|
? { capturedSelfiePath: navigationManager.captured.selfiePath }
|
|
52
52
|
: {})}
|
|
53
|
-
{...(configuration.continueButton
|
|
54
|
-
? {
|
|
55
|
-
continueButton: ({ onClick }: { onClick: () => void }) =>
|
|
56
|
-
configuration.continueButton?.({ onClick, enabled: true }),
|
|
57
|
-
}
|
|
58
|
-
: {})}
|
|
59
|
-
{...(configuration.cancelButton
|
|
60
|
-
? {
|
|
61
|
-
cancelButton: ({ onClick }: { onClick: () => void }) =>
|
|
62
|
-
configuration.cancelButton?.({ onClick, enabled: true }),
|
|
63
|
-
}
|
|
64
|
-
: {})}
|
|
65
53
|
onAccept={onNavigateNext}
|
|
66
54
|
onReject={() => {
|
|
67
55
|
if (configuration.allowRetake) onBack();
|
|
@@ -39,8 +39,8 @@ import {
|
|
|
39
39
|
* whether the partner constructs `network.retry` explicitly or not.
|
|
40
40
|
*/
|
|
41
41
|
const DEFAULT_RETRY: RetryConfig = Object.freeze({
|
|
42
|
-
maxAttempts:
|
|
43
|
-
strategy: RetryStrategy.
|
|
42
|
+
maxAttempts: 1,
|
|
43
|
+
strategy: RetryStrategy.none,
|
|
44
44
|
initialDelay: 1,
|
|
45
45
|
multiplier: 2,
|
|
46
46
|
maxDelay: 32,
|
|
@@ -147,6 +147,10 @@ export function FlowSuccessRenderer({
|
|
|
147
147
|
useUseSmileIDFonts();
|
|
148
148
|
const enableCrashReporting =
|
|
149
149
|
configuration.config?.enableCrashReporting !== false;
|
|
150
|
+
// Sandbox sessions are test traffic and must not pollute the production
|
|
151
|
+
// Sentry project; the reporter also drops debug bundles internally.
|
|
152
|
+
const useSandbox =
|
|
153
|
+
configuration.network?.config?.partnerConfig?.useSandbox === true;
|
|
150
154
|
|
|
151
155
|
// Per-flow session id used as the Sentry user-id so every event
|
|
152
156
|
// captured during this verification flow correlates under one issue
|
|
@@ -160,7 +164,10 @@ export function FlowSuccessRenderer({
|
|
|
160
164
|
|
|
161
165
|
useEffect(() => {
|
|
162
166
|
if (enableCrashReporting) {
|
|
163
|
-
|
|
167
|
+
// Tear down any prior client first so a changed useSandbox flag takes
|
|
168
|
+
// effect — enable() short-circuits when already enabled.
|
|
169
|
+
UseSmileIDCrashReporting.disable();
|
|
170
|
+
UseSmileIDCrashReporting.enable({ useSandbox });
|
|
164
171
|
UseSmileIDCrashReporting.setUserId(flowSessionIdRef.current!);
|
|
165
172
|
UseSmileIDCrashReporting.captureBreadcrumb({
|
|
166
173
|
category: "flow.navigation",
|
|
@@ -170,7 +177,7 @@ export function FlowSuccessRenderer({
|
|
|
170
177
|
return () => {
|
|
171
178
|
UseSmileIDCrashReporting.disable();
|
|
172
179
|
};
|
|
173
|
-
}, [enableCrashReporting]);
|
|
180
|
+
}, [enableCrashReporting, useSandbox]);
|
|
174
181
|
|
|
175
182
|
// `onResult` lives in a ref so the manager's `onExit` closure
|
|
176
183
|
// captures the latest partner callback without recreating the
|
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
InvalidJobTypeScreenConfigException,
|
|
21
21
|
MissingMLConfigurationBlockException,
|
|
22
22
|
} from "../../../../core/exception/job_validation_exceptions";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
AgentModeWithEnhancedLivenessException,
|
|
25
|
+
InvalidDocumentCaptureConfigException,
|
|
26
|
+
} from "../../../../core/exception/capture_validation_exceptions";
|
|
24
27
|
import {
|
|
25
28
|
runDocumentVerificationParamsValidation,
|
|
26
29
|
runEnhancedDocumentVerificationParamsValidation,
|
|
@@ -189,6 +192,63 @@ describe("FlowValidator — document capture config", () => {
|
|
|
189
192
|
});
|
|
190
193
|
});
|
|
191
194
|
|
|
195
|
+
describe("FlowValidator — selfie capture config", () => {
|
|
196
|
+
function selfieCaptureIssues(
|
|
197
|
+
configure: (s: {
|
|
198
|
+
allowAgentMode: boolean;
|
|
199
|
+
enableEnhancedLiveness: boolean;
|
|
200
|
+
}) => void,
|
|
201
|
+
) {
|
|
202
|
+
const config = new CaptureConfigBuilder();
|
|
203
|
+
config.captureType = CaptureType.selfie;
|
|
204
|
+
config.selfie(configure);
|
|
205
|
+
return runFlowValidation({
|
|
206
|
+
steps: [{ kind: "capture" as const, config }, processingStep()],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function agentModeIssues(
|
|
211
|
+
configure: (s: {
|
|
212
|
+
allowAgentMode: boolean;
|
|
213
|
+
enableEnhancedLiveness: boolean;
|
|
214
|
+
}) => void,
|
|
215
|
+
): AgentModeWithEnhancedLivenessException[] {
|
|
216
|
+
return selfieCaptureIssues(configure).filter(
|
|
217
|
+
(i): i is AgentModeWithEnhancedLivenessException =>
|
|
218
|
+
i instanceof AgentModeWithEnhancedLivenessException,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const rejectsAgentModePair = (
|
|
223
|
+
configure: Parameters<typeof agentModeIssues>[0],
|
|
224
|
+
): boolean => agentModeIssues(configure).length > 0;
|
|
225
|
+
|
|
226
|
+
it("rejects agent mode combined with enhanced liveness", () => {
|
|
227
|
+
const issues = agentModeIssues((s) => {
|
|
228
|
+
s.allowAgentMode = true;
|
|
229
|
+
s.enableEnhancedLiveness = true;
|
|
230
|
+
});
|
|
231
|
+
expect(issues).toHaveLength(1);
|
|
232
|
+
// Wire string partner tooling pivots on — a rename is a breaking change.
|
|
233
|
+
expect(issues[0]?.errorCode).toBe(
|
|
234
|
+
"BUILDER_AGENT_MODE_WITH_ENHANCED_LIVENESS",
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it("accepts agent mode and enhanced liveness on their own", () => {
|
|
239
|
+
expect(
|
|
240
|
+
rejectsAgentModePair((s) => {
|
|
241
|
+
s.allowAgentMode = true;
|
|
242
|
+
}),
|
|
243
|
+
).toBe(false);
|
|
244
|
+
expect(
|
|
245
|
+
rejectsAgentModePair((s) => {
|
|
246
|
+
s.enableEnhancedLiveness = true;
|
|
247
|
+
}),
|
|
248
|
+
).toBe(false);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
192
252
|
describe("FlowValidator — analyzer-per-capture-type", () => {
|
|
193
253
|
for (const { name, value: jobType } of SELFIE_JOB_TYPES) {
|
|
194
254
|
it(`${name}: missing ml block emits "is required" + MissingMLConfigurationBlockException`, () => {
|
|
@@ -10,7 +10,11 @@ import type {
|
|
|
10
10
|
UserDetails,
|
|
11
11
|
} from "../../../core/models/params";
|
|
12
12
|
import { JobType } from "../../../core/models/job_type";
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
DocumentCaptureConfigBuilder,
|
|
15
|
+
ScreenSpec,
|
|
16
|
+
SelfieCaptureConfigBuilder,
|
|
17
|
+
} from "../dsl/builder/screens_builder";
|
|
14
18
|
|
|
15
19
|
type ConsentScreenStep = Extract<ScreenSpec, { kind: "consent" }>;
|
|
16
20
|
import { IssueSeverity } from "../../../core/exception/issue_severity";
|
|
@@ -25,6 +29,7 @@ import {
|
|
|
25
29
|
MissingNetworkConfigurationBlockException,
|
|
26
30
|
} from "../../../core/exception/job_validation_exceptions";
|
|
27
31
|
import {
|
|
32
|
+
AgentModeWithEnhancedLivenessException,
|
|
28
33
|
InvalidDocumentCaptureConfigException,
|
|
29
34
|
InvalidMissingCaptureTypeException,
|
|
30
35
|
} from "../../../core/exception/capture_validation_exceptions";
|
|
@@ -69,6 +74,51 @@ type FlowValidatorInput = {
|
|
|
69
74
|
readonly userDetails?: UserDetails | undefined;
|
|
70
75
|
};
|
|
71
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Agent mode captures on the rear lens while the head-turn challenge is only
|
|
79
|
+
* validated for front-camera framing, so the pair is refused rather than
|
|
80
|
+
* silently dropping one of them.
|
|
81
|
+
*/
|
|
82
|
+
function selfieConfigIssues(
|
|
83
|
+
config: SelfieCaptureConfigBuilder | undefined,
|
|
84
|
+
): ValidationIssue[] {
|
|
85
|
+
if (config?.allowAgentMode && config.enableEnhancedLiveness) {
|
|
86
|
+
return [new AgentModeWithEnhancedLivenessException()];
|
|
87
|
+
}
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Surfaces at most one issue: missing block > missing type > bad aspect ratio. */
|
|
92
|
+
function documentConfigIssues(
|
|
93
|
+
config: DocumentCaptureConfigBuilder | undefined,
|
|
94
|
+
): ValidationIssue[] {
|
|
95
|
+
if (config === undefined) {
|
|
96
|
+
return [
|
|
97
|
+
new InvalidDocumentCaptureConfigException({
|
|
98
|
+
reason: "document is required when mode is Document",
|
|
99
|
+
}),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
if (config.documentType === undefined) {
|
|
103
|
+
return [
|
|
104
|
+
new InvalidDocumentCaptureConfigException({
|
|
105
|
+
reason: "documentType is required for document capture",
|
|
106
|
+
}),
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
if (
|
|
110
|
+
config.knownIdAspectRatio !== undefined &&
|
|
111
|
+
config.knownIdAspectRatio <= 0
|
|
112
|
+
) {
|
|
113
|
+
return [
|
|
114
|
+
new InvalidDocumentCaptureConfigException({
|
|
115
|
+
reason: "knownIdAspectRatio must be > 0 if provided",
|
|
116
|
+
}),
|
|
117
|
+
];
|
|
118
|
+
}
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
|
|
72
122
|
/**
|
|
73
123
|
* Quick triage of the in-progress builder state. Today the only rule
|
|
74
124
|
* surfaced is the empty-screens-block error — `b.screens(...)` must
|
|
@@ -113,32 +163,11 @@ export function runFlowValidation(
|
|
|
113
163
|
if (step.config.captureType === undefined) {
|
|
114
164
|
issues.push(new InvalidMissingCaptureTypeException());
|
|
115
165
|
}
|
|
116
|
-
|
|
117
|
-
|
|
166
|
+
if (step.config.captureType === CaptureType.selfie) {
|
|
167
|
+
issues.push(...selfieConfigIssues(step.config.selfieConfig));
|
|
168
|
+
}
|
|
118
169
|
if (step.config.captureType === CaptureType.document) {
|
|
119
|
-
|
|
120
|
-
if (documentConfig === undefined) {
|
|
121
|
-
issues.push(
|
|
122
|
-
new InvalidDocumentCaptureConfigException({
|
|
123
|
-
reason: "document is required when mode is Document",
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
} else if (documentConfig.documentType === undefined) {
|
|
127
|
-
issues.push(
|
|
128
|
-
new InvalidDocumentCaptureConfigException({
|
|
129
|
-
reason: "documentType is required for document capture",
|
|
130
|
-
}),
|
|
131
|
-
);
|
|
132
|
-
} else if (
|
|
133
|
-
documentConfig.knownIdAspectRatio !== undefined &&
|
|
134
|
-
documentConfig.knownIdAspectRatio <= 0
|
|
135
|
-
) {
|
|
136
|
-
issues.push(
|
|
137
|
-
new InvalidDocumentCaptureConfigException({
|
|
138
|
-
reason: "knownIdAspectRatio must be > 0 if provided",
|
|
139
|
-
}),
|
|
140
|
-
);
|
|
141
|
-
}
|
|
170
|
+
issues.push(...documentConfigIssues(step.config.documentConfig));
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
173
|
}
|