@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
@@ -3,6 +3,14 @@ import * as ScreenOrientation from "expo-screen-orientation";
3
3
  import { UseSmileIDCrashReporting } from "@smileid/usesmileid_bridge";
4
4
  import type { DocumentOrientation } from "../../../core/models/document_type";
5
5
 
6
+ export type CaptureOrientationOptions = {
7
+ /**
8
+ * Lock applied when the capture screen unmounts. Defaults to portrait-up;
9
+ * threaded from the builder's `exitOrientations`.
10
+ */
11
+ readonly exitLock?: ScreenOrientation.OrientationLock;
12
+ };
13
+
6
14
  /**
7
15
  * Lock device orientation for a capture screen, scoped to its lifetime
8
16
  * (`"Landscape"` → landscape, `"Portrait"` → portrait-up).
@@ -10,26 +18,76 @@ import type { DocumentOrientation } from "../../../core/models/document_type";
10
18
  * Each screen asserts its OWN orientation on mount, so document → selfie
11
19
  * reverts deterministically rather than depending on the previous
12
20
  * screen's teardown winning a race against the next camera initialising.
13
- * Unmount restores portrait-up as a safety net.
14
21
  *
15
22
  * Locks are global: the host app must permit the locked orientations at
16
23
  * the OS level (iOS `UISupportedInterfaceOrientations`, Android activity
17
- * `screenOrientation`) for the lock to take effect.
24
+ * `screenOrientation`) for the lock to take effect — a host that does not
25
+ * is warned about and reported rather than silently staying upright.
18
26
  */
19
27
  export function useCaptureOrientationLock(
20
28
  orientation: DocumentOrientation,
29
+ {
30
+ exitLock = ScreenOrientation.OrientationLock.PORTRAIT_UP,
31
+ }: CaptureOrientationOptions = {},
21
32
  ): void {
22
33
  useEffect(() => {
23
34
  const target =
24
35
  orientation === "Landscape"
25
36
  ? ScreenOrientation.OrientationLock.LANDSCAPE
26
37
  : ScreenOrientation.OrientationLock.PORTRAIT_UP;
38
+ void reportIfLockUnsupported(target);
27
39
  void lockOrientation(target);
28
40
 
29
41
  return () => {
30
- void lockOrientation(ScreenOrientation.OrientationLock.PORTRAIT_UP);
42
+ void lockOrientation(exitLock);
31
43
  };
32
- }, [orientation]);
44
+ }, [orientation, exitLock]);
45
+ }
46
+
47
+ /**
48
+ * Surface a host build that cannot honour `target`. `lockAsync` resolves
49
+ * successfully in that case, so without this preflight the capture screen
50
+ * just stays in the wrong orientation with no signal. (Expo's `app.json`
51
+ * `orientation` value is not such a case: the module's app-delegate hook
52
+ * outranks the baked `Info.plist` on iOS — device-verified.)
53
+ */
54
+ async function reportIfLockUnsupported(
55
+ target: ScreenOrientation.OrientationLock,
56
+ ): Promise<void> {
57
+ let supported: boolean;
58
+ try {
59
+ supported = await ScreenOrientation.supportsOrientationLockAsync(target);
60
+ } catch (error) {
61
+ reportOrientationFailure(error);
62
+ return;
63
+ }
64
+ if (supported) return;
65
+
66
+ // `typeof` guard: `__DEV__` is bundler-injected and absent outside Metro-like
67
+ // environments, where a bare reference throws rather than reading falsy.
68
+ const isDevBuild = typeof __DEV__ !== "undefined" && __DEV__;
69
+ if (isDevBuild && !warnedUnsupportedTargets.has(target)) {
70
+ warnedUnsupportedTargets.add(target);
71
+ console.warn(
72
+ "[UseSmileID] The capture screen needs to lock the device orientation, " +
73
+ "but this build does not permit it. Allow the required orientations " +
74
+ "in your app's iOS Info.plist / Android manifest.",
75
+ );
76
+ }
77
+ reportOrientationFailure(
78
+ new Error(`orientation lock unsupported by host build: ${target}`),
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Dedupes the dev warning per target: a flow remounts the capture screen for the back
84
+ * side and interstitial, and the misconfiguration can't change mid-session.
85
+ */
86
+ const warnedUnsupportedTargets = new Set<ScreenOrientation.OrientationLock>();
87
+
88
+ /** Test seam: clears the once-per-target dev-warning dedupe. */
89
+ export function resetUnsupportedLockWarningsForTesting(): void {
90
+ warnedUnsupportedTargets.clear();
33
91
  }
34
92
 
35
93
  /** Lock to `target`, routing a native failure through crash reporting. */
@@ -39,8 +97,12 @@ async function lockOrientation(
39
97
  try {
40
98
  await ScreenOrientation.lockAsync(target);
41
99
  } catch (error) {
42
- UseSmileIDCrashReporting.captureException(
43
- error instanceof Error ? error : new Error(String(error)),
44
- );
100
+ reportOrientationFailure(error);
45
101
  }
46
102
  }
103
+
104
+ function reportOrientationFailure(error: unknown): void {
105
+ UseSmileIDCrashReporting.captureException(
106
+ error instanceof Error ? error : new Error(String(error)),
107
+ );
108
+ }
@@ -28,11 +28,14 @@ import type {
28
28
  DocumentAnalyzer,
29
29
  FaceAnalyzer,
30
30
  } from "@smileid/usesmileid_platform_interface";
31
- import type { CaptureReadyEvent, ScanState } from "@smileid/usesmileid_bridge";
31
+ import type {
32
+ CaptureReadyEvent,
33
+ UseSmileIDScanState,
34
+ } from "@smileid/usesmileid_bridge";
32
35
  import {
33
36
  CaptureReadyEventKind,
34
37
  DocumentSide,
35
- ScanStateType,
38
+ UseSmileIDScanStateType,
36
39
  } from "@smileid/usesmileid_bridge";
37
40
  import { CaptureType } from "../../core/models/ml_config";
38
41
  import type { DocumentCaptureMode } from "../../core/models/document_capture_mode";
@@ -108,7 +111,7 @@ export type UseSmileIDCaptureScreenProps = {
108
111
  readonly onDocumentOrigin?: (origin: DocumentImageOrigin) => void;
109
112
 
110
113
  /** Fires when the state machine reaches `"timeout"`; the screen stops streaming. */
111
- readonly onCaptureTimeout?: (state: ScanState) => void;
114
+ readonly onCaptureTimeout?: (state: UseSmileIDScanState) => void;
112
115
 
113
116
  readonly onBack?: () => void;
114
117
  readonly showAttribution?: boolean;
@@ -116,6 +119,9 @@ export type UseSmileIDCaptureScreenProps = {
116
119
  /** Keeps the oval rim neutral through CAPTURING/PROCESSING — ESS has no green oval treatment. */
117
120
  readonly enableEnhancedLiveness?: boolean;
118
121
 
122
+ /** Runs a selfie capture on the rear camera. Ignored for document capture. */
123
+ readonly allowAgentMode?: boolean;
124
+
119
125
  /**
120
126
  * Document aspect ratio (width / height) for the document capture
121
127
  * guide cutout. Ignored for selfie capture. Omitted ⇒ the overlay's
@@ -162,9 +168,12 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
162
168
  const theme = props.theme ?? defaultLightTheme;
163
169
  const showAttribution = props.showAttribution ?? true;
164
170
  const enableEnhancedLiveness = props.enableEnhancedLiveness ?? false;
171
+ const allowAgentMode = props.allowAgentMode ?? false;
165
172
  // Literal union — useCameraDevice takes the lowercase string directly.
166
173
  const cameraFacing: "front" | "back" =
167
- props.captureType === CaptureType.selfie ? "front" : "back";
174
+ props.captureType === CaptureType.selfie && !allowAgentMode
175
+ ? "front"
176
+ : "back";
168
177
 
169
178
  const { hasPermission, requestPermission } = useCameraPermission();
170
179
  const device = useCameraDevice(cameraFacing);
@@ -202,8 +211,8 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
202
211
  }, [hasPermission, requestPermission]);
203
212
 
204
213
  // Per-frame scan state drives the overlay + feedback bubble.
205
- const [scanState, setScanState] = useState<ScanState>({
206
- type: ScanStateType.Initial,
214
+ const [scanState, setScanState] = useState<UseSmileIDScanState>({
215
+ type: UseSmileIDScanStateType.Initial,
207
216
  });
208
217
  const [streaming, setStreaming] = useState<boolean>(false);
209
218
  // Bumped on every Start tap to reset the feedback debouncer.
@@ -213,6 +222,13 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
213
222
  const [hasAnalyzedFrame, setHasAnalyzedFrame] = useState<boolean>(false);
214
223
  // Fallback shutter, revealed after activateManualAfterMs. Resets per mount.
215
224
  const [manualShutterRevealed, setManualShutterRevealed] = useState(false);
225
+ // Set when a document scan times out, so the recovery controls stay on screen
226
+ // after the scan (and `streaming`) stops. `timedOutRef` keeps the terminal
227
+ // branch idempotent without latching the one-shot delivery gate.
228
+ const [captureTimedOut, setCaptureTimedOut] = useState(false);
229
+ const timedOutRef = useRef(false);
230
+ // Set when streaming produced no analyzed frame at all, which the analyzer's own timeout cannot report.
231
+ const [captureStalled, setCaptureStalled] = useState(false);
216
232
  // True while a manual/gallery delivery is in flight — disables the controls.
217
233
  const [deliveringDocument, setDeliveringDocument] = useState(false);
218
234
  // Transient gallery-import error; overrides the live chip, self-clears so the user can re-pick.
@@ -232,7 +248,7 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
232
248
  const completedRef = useRef<boolean>(false);
233
249
  // Raw (not committed) frame state for the session-boundary check, so
234
250
  // transitions the 1% bucketing skips committing are still seen.
235
- const previousScanStateRef = useRef<ScanState | null>(null);
251
+ const previousScanStateRef = useRef<UseSmileIDScanState | null>(null);
236
252
 
237
253
  // Capture telemetry: wall-clock start (first analyzed frame) and a
238
254
  // rising-edge `unsatisfied` retry counter, surfaced via onCaptureComplete.
@@ -268,8 +284,28 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
268
284
  setSessionEpoch((epoch) => epoch + 1);
269
285
  // A new session must not compare against the old session's last state.
270
286
  previousScanStateRef.current = null;
271
- setStreaming(true);
272
- }, [streaming, onStartCapture]);
287
+ // Clear the terminal latches so a retry after a timeout or a stall is a fresh session.
288
+ const wasTerminal = timedOutRef.current;
289
+ timedOutRef.current = false;
290
+ setCaptureTimedOut(false);
291
+ setCaptureStalled(false);
292
+ setScanState({ type: UseSmileIDScanStateType.Initial });
293
+ setHasAnalyzedFrame(false);
294
+ // Frames banked before the abandoned session must not pad this one.
295
+ capturedEventsRef.current = [];
296
+ if (wasTerminal) {
297
+ // A timed-out native session is terminal — stream only after it resets,
298
+ // or the first retry frame re-reports Timeout and re-latches the error.
299
+ void props.analyzer
300
+ .reset()
301
+ .catch((error) => UseSmileIDCrashReporting.captureException(error))
302
+ .finally(() => {
303
+ if (!completedRef.current) setStreaming(true);
304
+ });
305
+ } else {
306
+ setStreaming(true);
307
+ }
308
+ }, [streaming, onStartCapture, props.analyzer]);
273
309
 
274
310
  const analyzer = props.analyzer;
275
311
  const onCaptureComplete = props.onCaptureComplete;
@@ -475,13 +511,14 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
475
511
  if (captureStartedAtRef.current === null) {
476
512
  captureStartedAtRef.current = Date.now();
477
513
  }
478
- const isUnsatisfied = result.state.type === ScanStateType.Unsatisfied;
514
+ const isUnsatisfied =
515
+ result.state.type === UseSmileIDScanStateType.Unsatisfied;
479
516
  if (isUnsatisfied && !wasUnsatisfiedRef.current) {
480
517
  captureRetriesRef.current += 1;
481
518
  }
482
519
  wasUnsatisfiedRef.current = isUnsatisfied;
483
520
 
484
- if (result.state.type === ScanStateType.Unsatisfied) {
521
+ if (result.state.type === UseSmileIDScanStateType.Unsatisfied) {
485
522
  capturedEventsRef.current = [];
486
523
  } else if (result.capturedEvents.length > 0) {
487
524
  capturedEventsRef.current = [
@@ -507,8 +544,9 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
507
544
 
508
545
  // Terminal states: finished → flush + complete; timeout → failure
509
546
  // without flushing (no captured artifacts to wait on).
510
- if (result.state.type === ScanStateType.Finished) {
511
- if (completedRef.current) return;
547
+ if (result.state.type === UseSmileIDScanStateType.Finished) {
548
+ // `timedOutRef` too: stopping the stream is async, so a frame queued before the timeout can still land here.
549
+ if (completedRef.current || timedOutRef.current) return;
512
550
  completedRef.current = true;
513
551
  setStreaming(false);
514
552
  // flush() waits for every emitted JPG to land on disk before we
@@ -524,10 +562,12 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
524
562
  onDocumentOrigin?.(DocumentImageOrigin.CameraAutoCapture);
525
563
  onCaptureComplete?.(capturedEventsRef.current, captureTelemetry());
526
564
  });
527
- } else if (result.state.type === ScanStateType.Timeout) {
528
- if (completedRef.current) return;
529
- completedRef.current = true;
565
+ } else if (result.state.type === UseSmileIDScanStateType.Timeout) {
566
+ if (completedRef.current || timedOutRef.current) return;
530
567
  setStreaming(false);
568
+ // The terminal state rides on `timedOutRef`, not the one-shot delivery gate, so the shutter and Start button stay usable.
569
+ timedOutRef.current = true;
570
+ setCaptureTimedOut(true);
531
571
  onCaptureTimeout?.(result.state);
532
572
  }
533
573
  },
@@ -549,7 +589,10 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
549
589
  sessionEpoch,
550
590
  );
551
591
  const isInstructionState =
552
- scanState.type === ScanStateType.Initial && !streaming;
592
+ scanState.type === UseSmileIDScanStateType.Initial && !streaming;
593
+ // A selfie timeout leaves `scanState` on Timeout, so the instruction predicate alone would hide the only way to retry.
594
+ const showStartButton =
595
+ (isInstructionState || captureTimedOut) && !isDocumentCapture;
553
596
  const canStart = !streaming && !completedRef.current;
554
597
 
555
598
  const previewReady = hasPermission && device != null;
@@ -564,6 +607,28 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
564
607
  [analyzer.frameProcessorPluginName],
565
608
  );
566
609
 
610
+ // Every terminal signal arrives inside a frame result, so a camera that delivers no frame at all would sit on the initial state forever.
611
+ // Latest-ref, not a dep: an inline `onCaptureTimeout` prop is a fresh identity every render, which would restart the wall clock.
612
+ const onCaptureTimeoutRef = useRef(onCaptureTimeout);
613
+ onCaptureTimeoutRef.current = onCaptureTimeout;
614
+ useEffect(() => {
615
+ if (!streaming || hasAnalyzedFrame) return;
616
+ const timer = setTimeout(() => {
617
+ if (completedRef.current || timedOutRef.current) return;
618
+ timedOutRef.current = true;
619
+ setCaptureStalled(true);
620
+ setCaptureTimedOut(true);
621
+ setStreaming(false);
622
+ onCaptureTimeoutRef.current?.({ type: UseSmileIDScanStateType.Timeout });
623
+ }, CameraDefaults.firstFrameTimeoutMs);
624
+ return () => clearTimeout(timer);
625
+ }, [streaming, hasAnalyzedFrame]);
626
+
627
+ // A frame disproves the stall; document capture has no Start button to clear it by hand.
628
+ useEffect(() => {
629
+ if (hasAnalyzedFrame) setCaptureStalled(false);
630
+ }, [hasAnalyzedFrame]);
631
+
567
632
  // A missing plugin means no frame ever produces a result — report once
568
633
  // on capture start rather than sitting silently on the initial state.
569
634
  const pluginErrorReportedRef = useRef(false);
@@ -687,13 +752,18 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
687
752
  // Document controls — live capture only (after Start). Manual shutter
688
753
  // follows the capture mode + fallback timer; gallery follows
689
754
  // allowGalleryUpload. Renderer also hides both during CAPTURING/PROCESSING.
755
+ // A timeout offers the shutter in every capture mode — it is the recovery
756
+ // affordance, so it outranks `captureMode` and survives `streaming` going
757
+ // false with the scan.
690
758
  const manualShutterAvailable =
691
759
  isDocumentCapture &&
692
- streaming &&
693
- (captureMode?.kind === "manualCapture" ||
694
- (captureMode?.kind === "autoCaptureWithManualFallback" &&
695
- manualShutterRevealed));
696
- const galleryAvailable = isDocumentCapture && streaming && allowGalleryUpload;
760
+ (captureTimedOut ||
761
+ (streaming &&
762
+ (captureMode?.kind === "manualCapture" ||
763
+ (captureMode?.kind === "autoCaptureWithManualFallback" &&
764
+ manualShutterRevealed))));
765
+ const galleryAvailable =
766
+ isDocumentCapture && (streaming || captureTimedOut) && allowGalleryUpload;
697
767
 
698
768
  // Deps are scalar fields (mapCaptureUiState returns a fresh object per
699
769
  // render). captureProgress intentionally stays a separate per-frame prop.
@@ -704,10 +774,12 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
704
774
  enableEnhancedLiveness,
705
775
  animationDirection: uiState.animationDirection,
706
776
  poseMatched: uiState.poseMatched,
707
- // A transient gallery-import error preempts the live feedback chip.
708
- feedbackKey: galleryErrorKey ?? settledFeedbackKey,
777
+ // A stalled camera, then a transient gallery-import error, preempt the live feedback chip.
778
+ feedbackKey: captureStalled
779
+ ? "si_capture_camera_unavailable"
780
+ : (galleryErrorKey ?? settledFeedbackKey),
709
781
  // Documents auto-start (no Start button); selfie keeps its.
710
- showCaptureButton: isInstructionState && !isDocumentCapture,
782
+ showCaptureButton: showStartButton,
711
783
  showManualShutter: manualShutterAvailable,
712
784
  showGalleryUpload: galleryAvailable,
713
785
  isBackSide: documentSide === DocumentSide.Back,
@@ -726,7 +798,8 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
726
798
  uiState.poseMatched,
727
799
  settledFeedbackKey,
728
800
  galleryErrorKey,
729
- isInstructionState,
801
+ captureStalled,
802
+ showStartButton,
730
803
  isDocumentCapture,
731
804
  manualShutterAvailable,
732
805
  galleryAvailable,
@@ -806,8 +879,14 @@ export function UseSmileIDCaptureScreen(props: UseSmileIDCaptureScreenProps) {
806
879
  <UseSmileIDThemeProvider value={theme}>
807
880
  {/* Translucent status bar → full-bleed window so the oval's reference
808
881
  frame is the full screen (no-op on iOS). On-device-pending: residual
809
- inset also depends on the host SafeAreaProvider config. */}
810
- <StatusBar translucent backgroundColor="transparent" />
882
+ inset also depends on the host SafeAreaProvider config.
883
+ `dark-content` because the capture overlay draws a near-opaque white
884
+ scrim behind the status bar, whatever the host app's own bar style. */}
885
+ <StatusBar
886
+ translucent
887
+ backgroundColor="transparent"
888
+ barStyle="dark-content"
889
+ />
811
890
  <UseSmileIDCaptureContent
812
891
  state={contentState}
813
892
  captureProgress={uiState.captureProgress}
@@ -26,6 +26,7 @@ import { UseSmileIDLearnMoreContent } from "../components/use_smile_id_learn_mor
26
26
  import { UseSmileIDPartnerIcon } from "../components/use_smile_id_partner_icon";
27
27
  import { UseSmileIDRoundedCheckbox } from "../components/use_smile_id_rounded_checkbox";
28
28
  import { useUseSmileIDLocalizations } from "../../l10n/use_smile_id_localizations_provider";
29
+ import { GlobalPrivacyControl } from "../../core/models/global_privacy_control";
29
30
  import { getDefaultConsentState, getDeviceLocale } from "../utils/locale_utils";
30
31
  import {
31
32
  buildConsentRows,
@@ -59,6 +60,12 @@ export type UseSmileIDConsentScreenProps = {
59
60
  readonly showAttribution?: boolean;
60
61
  readonly onAllow?: (info: ConsentInformation) => void;
61
62
  readonly onDeny?: () => void;
63
+ /**
64
+ * Partner-declared consent posture; seeds the checkbox's initial state
65
+ * via `getDefaultConsentState`. Defaults to the universal opt-out
66
+ * baseline (`GlobalPrivacyControl.OptOut`).
67
+ */
68
+ readonly globalPrivacyControl?: GlobalPrivacyControl;
62
69
  readonly theme?: UseSmileIDTheme;
63
70
  /**
64
71
  * Render the bottom-pinned Allow button via a partner-supplied
@@ -107,11 +114,15 @@ export function UseSmileIDConsentScreen(props: UseSmileIDConsentScreenProps) {
107
114
  const loc = useUseSmileIDLocalizations();
108
115
  const showAttribution = props.showAttribution ?? true;
109
116
 
110
- // Locale-aware default: ticked outside the EU (`true`), unticked
111
- // inside (`false`) to honour GDPR opt-in. `getDefaultConsentState()`
112
- // reads the resolved BCP-47 locale and checks the country segment
113
- // against the EU member-state set.
114
- const [consented, setConsented] = useState(() => getDefaultConsentState());
117
+ const globalPrivacyControl =
118
+ props.globalPrivacyControl ?? GlobalPrivacyControl.OptOut;
119
+
120
+ // Posture-aware default: the opt-out baseline starts ticked everywhere;
121
+ // the legacy opt-in posture consults the resolved BCP-47 locale and
122
+ // starts unticked for EU member states (GDPR opt-in).
123
+ const [consented, setConsented] = useState(() =>
124
+ getDefaultConsentState(globalPrivacyControl),
125
+ );
115
126
  const [learnMoreOpen, setLearnMoreOpen] = useState(false);
116
127
 
117
128
  const subtitleParts = composeConsentSubtitleParts(props.partnerName, loc);
@@ -1,7 +1,8 @@
1
- import { Pressable, StyleSheet, Text, View } from "react-native";
1
+ import { StyleSheet, Text, View } from "react-native";
2
2
  import { SafeAreaView } from "react-native-safe-area-context";
3
3
  import type { UseSmileIDTheme } from "../theme/theme";
4
4
  import { useUseSmileIDTheme } from "../theme/theme_context";
5
+ import { UseSmileIDAttribution } from "../components/use_smile_id_attribution";
5
6
  import { UseSmileIDBackButton } from "../components/use_smile_id_back_button";
6
7
  import { UseSmileIDButton } from "../components/use_smile_id_button";
7
8
  import { UseSmileIDInstructionsHero } from "../components/use_smile_id_instructions_hero";
@@ -48,7 +49,7 @@ export function UseSmileIDDocumentBackInstructions(
48
49
  styles.root,
49
50
  {
50
51
  backgroundColor: theme.colors.background,
51
- paddingHorizontal: theme.spacing.large,
52
+ paddingHorizontal: theme.spacing.medium,
52
53
  paddingVertical: theme.spacing.medium,
53
54
  },
54
55
  ]}
@@ -70,12 +71,15 @@ export function UseSmileIDDocumentBackInstructions(
70
71
  ...theme.typography.pageHeading,
71
72
  fontWeight: "800",
72
73
  color: theme.colors.titleText,
74
+ textAlign: "center",
73
75
  }}
74
76
  testID="si_document_back_instructions_title"
75
77
  >
76
78
  {localizations.siDocumentFlipTitle(props.documentTypeDisplayName)}
77
79
  </Text>
78
80
 
81
+ <View style={{ height: theme.spacing.medium }} />
82
+
79
83
  <View style={styles.hero} testID="si_document_back_instructions_hero">
80
84
  <UseSmileIDInstructionsHero
81
85
  source={heroSource}
@@ -98,24 +102,17 @@ export function UseSmileIDDocumentBackInstructions(
98
102
  {allowSkip ? (
99
103
  <>
100
104
  <View style={{ height: theme.spacing.small }} />
101
- <Pressable
102
- onPress={onSkip}
103
- accessibilityRole="button"
105
+ <UseSmileIDButton
106
+ label={localizations.siDocumentSkipBack}
107
+ onClick={onSkip}
108
+ variant="secondary"
109
+ theme={theme}
104
110
  testID="si_document_skip_back_button"
105
- style={styles.skip}
106
- >
107
- <Text
108
- style={{
109
- ...theme.typography.button,
110
- color: theme.colors.primary,
111
- textAlign: "center",
112
- }}
113
- >
114
- {localizations.siDocumentSkipBack}
115
- </Text>
116
- </Pressable>
111
+ />
117
112
  </>
118
113
  ) : null}
114
+ <View style={{ height: theme.spacing.small }} />
115
+ <UseSmileIDAttribution theme={theme} />
119
116
  </View>
120
117
  </SafeAreaView>
121
118
  );
@@ -125,10 +122,4 @@ const styles = StyleSheet.create({
125
122
  root: { flex: 1 },
126
123
  hero: { flex: 1 },
127
124
  bottom: { alignItems: "center" },
128
- skip: {
129
- height: 48,
130
- alignItems: "center",
131
- justifyContent: "center",
132
- alignSelf: "stretch",
133
- },
134
125
  });
@@ -47,7 +47,7 @@ export function UseSmileIDDocumentPreviewScreen(
47
47
  edges={["left", "right", "bottom"]}
48
48
  style={[
49
49
  styles.root,
50
- { backgroundColor: theme.colors.captureReviewBackground },
50
+ { backgroundColor: theme.colors.background },
51
51
  ]}
52
52
  testID="si_document_preview_screen"
53
53
  >
@@ -1,4 +1,4 @@
1
- import { useState, type ReactNode } from "react";
1
+ import { useState } from "react";
2
2
  import { StyleSheet, Text, View, type LayoutChangeEvent } from "react-native";
3
3
  import { SafeAreaView } from "react-native-safe-area-context";
4
4
  import type { UseSmileIDTheme } from "../theme/theme";
@@ -16,28 +16,13 @@ export type UseSmileIDPreviewScreenProps = {
16
16
  readonly showAttribution?: boolean;
17
17
  readonly onAccept?: () => void;
18
18
  readonly onReject?: () => void;
19
- /**
20
- * Render the Accept button via a partner-supplied slot.
21
- *
22
- * **Slot contract:** providing *any* slot (continue or cancel)
23
- * replaces the *entire* default confirmation row. If you supply
24
- * `continueButton` and want a Retake control too, supply
25
- * `cancelButton` as well — the default Retake tile does not
26
- * fall through.
27
- */
28
- readonly continueButton?: (props: { onClick: () => void }) => ReactNode;
29
- /**
30
- * Render the Reject (retake) button via a partner-supplied slot.
31
- * See the slot contract note on `continueButton`.
32
- */
33
- readonly cancelButton?: (props: { onClick: () => void }) => ReactNode;
34
19
  readonly theme?: UseSmileIDTheme;
35
20
  };
36
21
 
37
22
  /**
38
23
  * Preview screen — confirms the captured selfie before submission.
39
24
  *
40
- * - Uniform page (`captureReviewBackground`, theme-following) with the
25
+ * - Uniform page (`background`, theme-following) with the
41
26
  * captured image clipped to the face oval. The oval scales to the
42
27
  * leftover height above the card so the two never overlap; no scrim
43
28
  * is painted.
@@ -74,16 +59,12 @@ export function UseSmileIDPreviewScreen(props: UseSmileIDPreviewScreenProps) {
74
59
  if (state.canRetake) props.onReject?.();
75
60
  };
76
61
 
77
- const customContinue = props.continueButton;
78
- const customCancel = props.cancelButton;
79
- const hasSlots = Boolean(customContinue || customCancel);
80
-
81
62
  return (
82
63
  <SafeAreaView
83
64
  edges={["left", "right", "bottom"]}
84
65
  style={[
85
66
  styles.root,
86
- { backgroundColor: theme.colors.captureReviewBackground },
67
+ { backgroundColor: theme.colors.background },
87
68
  ]}
88
69
  testID="si_preview_screen"
89
70
  onLayout={onScreenLayout}
@@ -122,23 +103,16 @@ export function UseSmileIDPreviewScreen(props: UseSmileIDPreviewScreenProps) {
122
103
  {loc.siPreviewSubtitle}
123
104
  </Text>
124
105
  <View style={{ marginTop: theme.spacing.medium }}>
125
- {hasSlots ? (
126
- <View style={styles.buttonRow}>
127
- {customCancel ? customCancel({ onClick: handleReject }) : null}
128
- {customContinue
129
- ? customContinue({ onClick: handleAccept })
130
- : null}
131
- </View>
132
- ) : (
133
- <UseSmileIDConfirmationButtons
134
- onAccept={handleAccept}
135
- onReject={handleReject}
136
- acceptLabel={loc.siPreviewConfirm}
137
- rejectLabel={loc.siPreviewRetake}
138
- theme={theme}
139
- testID="si_preview_confirmation_buttons"
140
- />
141
- )}
106
+ {/* The confirm/retake buttons are the fixed icon pair and are not
107
+ customisable. */}
108
+ <UseSmileIDConfirmationButtons
109
+ onAccept={handleAccept}
110
+ onReject={handleReject}
111
+ acceptLabel={loc.siPreviewConfirm}
112
+ rejectLabel={loc.siPreviewRetake}
113
+ theme={theme}
114
+ testID="si_preview_confirmation_buttons"
115
+ />
142
116
  </View>
143
117
  </UseSmileIDCardSurface>
144
118
 
@@ -150,10 +124,4 @@ export function UseSmileIDPreviewScreen(props: UseSmileIDPreviewScreenProps) {
150
124
  const styles = StyleSheet.create({
151
125
  root: { flex: 1, overflow: "hidden" },
152
126
  card: { alignItems: "center" },
153
- buttonRow: {
154
- flexDirection: "row",
155
- alignSelf: "stretch",
156
- justifyContent: "space-between",
157
- alignItems: "center",
158
- },
159
127
  });
@@ -110,7 +110,7 @@ export function UseSmileIDProcessingScreen(
110
110
  <SafeAreaView
111
111
  style={[
112
112
  styles.root,
113
- { backgroundColor: theme.colors.captureReviewBackground },
113
+ { backgroundColor: theme.colors.background },
114
114
  ]}
115
115
  testID="si_processing_screen"
116
116
  onLayout={onScreenLayout}