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