@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
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
2
2
  import {
3
3
  Animated,
4
4
  Easing,
5
+ Platform,
5
6
  StyleSheet,
6
7
  View,
7
8
  type LayoutChangeEvent,
@@ -160,6 +161,13 @@ export function UseSmileIDInstructionsHero({
160
161
  // motion is reduced; otherwise let it play on its own clock (autoPlay+loop).
161
162
  const lottieDrivenByProgress = showOvalProgress || reducedMotion;
162
163
 
164
+ // lottie-ios's Core Animation engine mis-renders the document heroes' track mattes,
165
+ // so oval-less heroes use the main-thread engine on iOS; Android keeps its default.
166
+ const renderModeProps =
167
+ Platform.OS === "ios" && !showOval
168
+ ? ({ renderMode: "SOFTWARE" } as const)
169
+ : {};
170
+
163
171
  // Inset the ring by half the stroke so it isn't clipped at the SVG edge.
164
172
  const ellipseProps = {
165
173
  cx: ovalWidth / 2,
@@ -196,6 +204,7 @@ export function UseSmileIDInstructionsHero({
196
204
  autoPlay={!lottieDrivenByProgress}
197
205
  loop={!lottieDrivenByProgress}
198
206
  {...(lottieDrivenByProgress ? { progress: heroProgress } : {})}
207
+ {...renderModeProps}
199
208
  resizeMode="contain"
200
209
  style={{ width: cardWidth, height: cardHeight }}
201
210
  />
@@ -266,6 +275,7 @@ export function UseSmileIDInstructionsHero({
266
275
  autoPlay={!lottieDrivenByProgress}
267
276
  loop={!lottieDrivenByProgress}
268
277
  {...(lottieDrivenByProgress ? { progress: heroProgress } : {})}
278
+ {...renderModeProps}
269
279
  style={{
270
280
  position: "absolute",
271
281
  width: animationSize,
@@ -74,15 +74,15 @@ describe("FlowNavigationManager — document per-side preview loop", () => {
74
74
  true,
75
75
  );
76
76
 
77
- const passportFlow = buildScreens((screens) => {
77
+ const greenBookFlow = buildScreens((screens) => {
78
78
  screens.capture((capture) => {
79
79
  capture.captureType = CaptureType.document;
80
80
  capture.document((document) => {
81
- document.documentType = DocumentType.Passport; // no back side
81
+ document.documentType = DocumentType.SouthAfricaGreenBook; // no back side
82
82
  });
83
83
  });
84
84
  });
85
- expect(makeManager(passportFlow).documentCapturesBack()).toBe(false);
85
+ expect(makeManager(greenBookFlow).documentCapturesBack()).toBe(false);
86
86
  });
87
87
 
88
88
  it("after the FRONT preview, routes back to the document capture for the BACK", () => {
@@ -21,7 +21,12 @@ import {
21
21
  } from "../../../core/models/result";
22
22
  import { JobType } from "../../../core/models/job_type";
23
23
  import { DocumentType } from "../../../core/models/document_type";
24
- import { MissingRequiredFieldException } from "../../../core/exception";
24
+ import { GlobalPrivacyControl } from "../../../core/models/global_privacy_control";
25
+ import {
26
+ MissingJobTypeException,
27
+ MissingRequiredFieldException,
28
+ UseSmileIDErrorCode,
29
+ } from "../../../core/exception";
25
30
  import type { UseSmileIDAnalyticsEvent } from "../analytics/use_smile_id_analytics_event";
26
31
 
27
32
  function buildScreens(block: (s: ScreensBuilder) => void): ScreensConfig {
@@ -528,6 +533,41 @@ describe("FlowNavigationManager — submission lifecycle", () => {
528
533
  expect(manager.jobSubmissionState.kind).toBe("failure");
529
534
  });
530
535
 
536
+ it("an unrecognised jobType fails submission with the typed builder exception", async () => {
537
+ const repository = new StubRepository();
538
+ const manager = makeManager(
539
+ buildScreens((screens) => screens.processing()),
540
+ vi.fn(),
541
+ {
542
+ userDetails: SAMPLE_USER_DETAILS,
543
+ network: {
544
+ config: {
545
+ jobType: 99 as JobType,
546
+ token: "k",
547
+ partnerConfig: {
548
+ partnerId: "p",
549
+ callbackUrl: "",
550
+ useSandbox: true,
551
+ },
552
+ },
553
+ },
554
+ },
555
+ repository,
556
+ );
557
+
558
+ manager.beginSubmissionIfNeeded();
559
+ await flushMicrotasks();
560
+ expect(manager.jobSubmissionState.kind).toBe("failure");
561
+ if (manager.jobSubmissionState.kind === "failure") {
562
+ expect(manager.jobSubmissionState.error).toBeInstanceOf(
563
+ MissingJobTypeException,
564
+ );
565
+ expect(
566
+ (manager.jobSubmissionState.error as MissingJobTypeException).errorCode,
567
+ ).toBe(UseSmileIDErrorCode.Builder.MissingJobType);
568
+ }
569
+ });
570
+
531
571
  it("beginSubmissionIfNeeded is idempotent — second call while loading is a no-op", async () => {
532
572
  const repository = new StubRepository();
533
573
  repository.manual = true;
@@ -602,6 +642,55 @@ describe("FlowNavigationManager — submission lifecycle", () => {
602
642
  expect(manager.jobSubmissionState.kind).toBe("success");
603
643
  });
604
644
 
645
+ it("retrySubmission double-trigger fires exactly one new submission", async () => {
646
+ // Concrete scenario: the processing screen's retry button is
647
+ // double-tapped after a failed submission. The second tap must not
648
+ // reset the in-flight `loading` latch and launch a duplicate job.
649
+ const repository = new StubRepository();
650
+ repository.result = failure(new Error("transient"));
651
+ const manager = makeManager(
652
+ buildScreens((screens) => screens.processing()),
653
+ vi.fn(),
654
+ {
655
+ enhancedKYCParams: {
656
+ idType: "BVN",
657
+ idNumber: "1",
658
+ country: "NG",
659
+ },
660
+ userDetails: SAMPLE_USER_DETAILS,
661
+ network: {
662
+ config: {
663
+ jobType: JobType.enhancedKyc,
664
+ token: "k",
665
+ partnerConfig: {
666
+ partnerId: "p",
667
+ callbackUrl: "",
668
+ useSandbox: true,
669
+ },
670
+ },
671
+ },
672
+ },
673
+ repository,
674
+ );
675
+
676
+ // Initial attempt fails, leaving the retry affordance visible.
677
+ manager.beginSubmissionIfNeeded();
678
+ await flushMicrotasks();
679
+ expect(manager.jobSubmissionState.kind).toBe("failure");
680
+ expect(repository.received).toHaveLength(1);
681
+
682
+ // The retried submission stays in flight so both taps land while
683
+ // the request is loading.
684
+ repository.manual = true;
685
+ manager.retrySubmission();
686
+ manager.retrySubmission(); // double-tap — ignored while in flight
687
+
688
+ await flushMicrotasks();
689
+ expect(manager.jobSubmissionState.kind).toBe("loading");
690
+ // Exactly one new submission: the initial failure plus one retry.
691
+ expect(repository.received).toHaveLength(2);
692
+ });
693
+
605
694
  it("does not update state if the manager is disposed mid-submission", async () => {
606
695
  const repository = new StubRepository();
607
696
  repository.manual = true;
@@ -889,6 +978,288 @@ describe("FlowNavigationManager — request assembly per JobType", () => {
889
978
  });
890
979
  });
891
980
 
981
+ describe("FlowNavigationManager — token-provided user details & consent (assembly)", () => {
982
+ // Mint a structurally valid JWT with a caller-supplied payload — only
983
+ // the base64url-encoded middle segment is decoded.
984
+ function makeJwt(payload: Record<string, unknown>): string {
985
+ const payloadSegment = Buffer.from(JSON.stringify(payload)).toString(
986
+ "base64url",
987
+ );
988
+ return `eyJhbGciOiJub25lIn0.${payloadSegment}.stub-sig`;
989
+ }
990
+
991
+ const completeConsentClaim = {
992
+ granted: true,
993
+ granted_at: "2026-01-01T09:00:00.000Z",
994
+ notice_language: "EN",
995
+ notice_privacy_policy_url: "https://partner.test/privacy",
996
+ } as const;
997
+
998
+ const fullBindingToken = makeJwt({
999
+ payload: {
1000
+ given_names: "pii_abc",
1001
+ last_name: "pii_def",
1002
+ email: "pii_ghi",
1003
+ phone_number: "pii_jkl",
1004
+ consent: completeConsentClaim,
1005
+ },
1006
+ });
1007
+
1008
+ // Real tokens minted by the /v3/token deployment (same fixtures as
1009
+ // use_smile_id_token_decoder.test.ts): ES256-signed, expired minutes
1010
+ // after minting, PII values as opaque vault references. Decoded through
1011
+ // the production decoder so the whole flow — decode, screen skip,
1012
+ // assembly reduction — runs exactly as in production.
1013
+ const MINTED_FULL_BINDING_TOKEN =
1014
+ "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
1015
+ "eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiY291bnRyeSI6Ik5HIiwiaWRfdHlwZSI6Ik5JTl9WMiIsImVtYWlsIjoicGlpXzAxa3Azczl0NzhmZXg5MXFoNDZ5cDg3cmpxIiwiZ2l2ZW5fbmFtZXMiOiJwaWlfMDFrenJtMTcwdGY2OXJxOTUzOHJ6Z2V0eXoiLCJpZF9udW1iZXIiOiJwaWlfMDFra2dhYTliNWZ2Z2EyMnllZXM1ZjRyNnQiLCJsYXN0X25hbWUiOiJwaWlfMDFrcDNzOXQ3cmU4aHI2ZGVyOXEzbnB6N2MiLCJwaG9uZV9udW1iZXIiOiJwaWlfMDFrenJtdHdkeGZlenJ0dGRmOXQzc3Rzem4iLCJjYWxsYmFja191cmwiOiJjYWxsYmFja18wMWt6cm10d2VmZTV5YWgzbmR2NW03MDc3ZyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDYwNzEyLCJpYXQiOjE3ODY0NTk4MTIsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
1016
+ "cuZgo64eEt5vNvND_RvCVpJyTv4rqax9ebkLV4i4uI4raz6fBhazxspbaoErYC0wxwRmn5EhPAdAdDnNKI6tEQ";
1017
+
1018
+ const MINTED_NAMES_AND_EMAIL_TOKEN =
1019
+ "eyJhbGciOiJFUzI1NiIsImtpZCI6ImtleV8wMWtibWd6bXczZXloOW5jdjlqcG1kNDlqZiIsInR5cCI6IkpXVCJ9." +
1020
+ "eyJhcGlfdXJsIjoiaHR0cHM6Ly92My10b2tlbi1jb25zZW50LXBheWxvYWQuYXBpLnNtaWxlaWQuY28vdjMiLCJrZXlfaWQiOiIyY2NjOWMyMy01Y2U3LTRkMGUtYWRkYy1lN2I1NGQ4YjkxZGQiLCJwYXlsb2FkIjp7ImNvbnNlbnQiOnsiZ3JhbnRlZCI6dHJ1ZSwiZ3JhbnRlZF9hdCI6IjIwMjYtMDEtMDFUMDk6MDA6MDAuMDAwWiIsIm5vdGljZV9sYW5ndWFnZSI6IkVOIiwibm90aWNlX3ByaXZhY3lfcG9saWN5X3VybCI6Imh0dHBzOi8vanVtYS5wcml2YWN5LXBvbGljeS5jb20ifSwiZW1haWwiOiJwaWlfMDFrcDNzOXQ3OGZleDkxcWg0NnlwODdyanEiLCJnaXZlbl9uYW1lcyI6InBpaV8wMWt6cm0xNzB0ZjY5cnE5NTM4cnpnZXR5eiIsImxhc3RfbmFtZSI6InBpaV8wMWtwM3M5dDdyZThocjZkZXI5cTNucHo3YyJ9LCJwYXJ0bmVyX2lkIjoiMTQ4MyIsInBvbGljeSI6MTUsImF1ZCI6InNtaWxlaWQtYXBpIiwiZXhwIjoxNzg2NDU5ODcxLCJpYXQiOjE3ODY0NTg5NzEsImlzcyI6InNtaWxlaWQtYXV0aCJ9." +
1021
+ "s5BFthU2B1aKxYDYyyWYDmRpLoHibH34r2Lk4OS-wZQC8pmYROjWBiORx6n2_0srmxz_SA2eZo_FHyAr-ew_Zg";
1022
+
1023
+ function assemble(
1024
+ token: string,
1025
+ extraConfig: Partial<FlowConfigurationSnapshot> = {},
1026
+ seedManager: (m: FlowNavigationManager) => void = () => {},
1027
+ ): {
1028
+ readonly repository: StubRepository;
1029
+ readonly manager: FlowNavigationManager;
1030
+ } {
1031
+ const repository = new StubRepository();
1032
+ const manager = makeManager(
1033
+ buildScreens((screens) => screens.processing()),
1034
+ vi.fn(),
1035
+ {
1036
+ enhancedKYCParams: {
1037
+ idType: "PASSPORT",
1038
+ idNumber: "X1",
1039
+ country: "NG",
1040
+ },
1041
+ ...extraConfig,
1042
+ network: {
1043
+ config: {
1044
+ jobType: JobType.enhancedKyc,
1045
+ token,
1046
+ partnerConfig: {
1047
+ partnerId: "p",
1048
+ callbackUrl: "",
1049
+ useSandbox: true,
1050
+ },
1051
+ },
1052
+ },
1053
+ },
1054
+ repository,
1055
+ );
1056
+ seedManager(manager);
1057
+ manager.beginSubmissionIfNeeded();
1058
+ return { repository, manager };
1059
+ }
1060
+
1061
+ it("assembles without userDetails when the token binds names + a contact field", () => {
1062
+ const { repository } = assemble(fullBindingToken);
1063
+ expect(repository.received).toHaveLength(1);
1064
+ const request = repository.received[0]!;
1065
+ expect(request.userDetails).toBeUndefined();
1066
+ });
1067
+
1068
+ it("omits the consent record when token consent is complete and no local source exists", () => {
1069
+ // The DEFAULT_CONSENT granted:false placeholder must NOT be emitted
1070
+ // on the token-complete path — the server injects the token's consent.
1071
+ const { repository } = assemble(fullBindingToken, {
1072
+ userDetails: SAMPLE_USER_DETAILS,
1073
+ });
1074
+ const request = repository.received[0]!;
1075
+ expect(request.consent).toBeUndefined();
1076
+ });
1077
+
1078
+ it("still throws MissingRequiredFieldException(userDetails) when the token binds names only", async () => {
1079
+ const namesOnlyToken = makeJwt({
1080
+ payload: { given_names: "pii_abc", last_name: "pii_def" },
1081
+ });
1082
+ const { repository, manager } = assemble(namesOnlyToken);
1083
+ await flushMicrotasks();
1084
+ expect(repository.received).toHaveLength(0);
1085
+ const state = manager.jobSubmissionState;
1086
+ expect(state.kind).toBe("failure");
1087
+ if (state.kind === "failure") {
1088
+ expect(state.error).toBeInstanceOf(MissingRequiredFieldException);
1089
+ expect(
1090
+ (state.error as MissingRequiredFieldException).errorDetails,
1091
+ ).toEqual({ fieldName: "userDetails" });
1092
+ }
1093
+ });
1094
+
1095
+ it("keeps the granted:false placeholder when token consent is only partial", () => {
1096
+ const partialConsentToken = makeJwt({
1097
+ payload: {
1098
+ given_names: "pii_abc",
1099
+ last_name: "pii_def",
1100
+ email: "pii_ghi",
1101
+ consent: { granted: true, granted_at: "2026-01-01T09:00:00.000Z" },
1102
+ },
1103
+ });
1104
+ const { repository } = assemble(partialConsentToken);
1105
+ const request = repository.received[0]!;
1106
+ expect(request.consent).toEqual({
1107
+ granted: false,
1108
+ grantedAt: "",
1109
+ noticeLanguage: "",
1110
+ noticePrivacyPolicyUrl: "",
1111
+ });
1112
+ });
1113
+
1114
+ it("a complete token consent omits the consent record even when a local one exists", () => {
1115
+ // Token data always wins: even a captured local consent is not sent —
1116
+ // the part is omitted and the server injects the token's record.
1117
+ const { repository } = assemble(
1118
+ fullBindingToken,
1119
+ { userDetails: SAMPLE_USER_DETAILS },
1120
+ (m) => m.setConsent(consentInfo),
1121
+ );
1122
+ const request = repository.received[0]!;
1123
+ expect(request.consent).toBeUndefined();
1124
+ });
1125
+
1126
+ it("a complete token consent drops the consent screen from the flow", () => {
1127
+ const repository = new StubRepository();
1128
+ const manager = makeManager(
1129
+ buildScreens((screens) => {
1130
+ screens.consent((c) => {
1131
+ c.partnerName = "Acme";
1132
+ c.partnerPrivacyPolicyUrl = "https://example.test/privacy";
1133
+ });
1134
+ screens.capture((c) => {
1135
+ c.captureType = CaptureType.selfie;
1136
+ });
1137
+ }),
1138
+ vi.fn(),
1139
+ {
1140
+ userDetails: SAMPLE_USER_DETAILS,
1141
+ network: {
1142
+ config: {
1143
+ jobType: JobType.smartSelfieEnrollment,
1144
+ token: fullBindingToken,
1145
+ partnerConfig: {
1146
+ partnerId: "p",
1147
+ callbackUrl: "",
1148
+ useSandbox: true,
1149
+ },
1150
+ },
1151
+ },
1152
+ },
1153
+ repository,
1154
+ );
1155
+ expect(manager.currentScreen?.kind).toBe("capture");
1156
+ expect(manager.navigationPath.some((step) => step.kind === "consent")).toBe(
1157
+ false,
1158
+ );
1159
+ });
1160
+
1161
+ it("a preset consentInformation is still sent in full when token consent is partial", () => {
1162
+ const partialConsentToken = makeJwt({
1163
+ payload: { consent: { granted: true } },
1164
+ });
1165
+ const { repository } = assemble(partialConsentToken, {
1166
+ userDetails: SAMPLE_USER_DETAILS,
1167
+ consentInformation: consentInfo,
1168
+ });
1169
+ const request = repository.received[0]!;
1170
+ expect(request.consent).toEqual(consentInfo);
1171
+ });
1172
+
1173
+ it("the minted full-binding token drives the flow end to end with nothing local", () => {
1174
+ const repository = new StubRepository();
1175
+ const manager = makeManager(
1176
+ buildScreens((screens) => {
1177
+ screens.consent((c) => {
1178
+ c.partnerName = "Acme";
1179
+ c.partnerPrivacyPolicyUrl = "https://example.test/privacy";
1180
+ });
1181
+ screens.capture((c) => {
1182
+ c.captureType = CaptureType.selfie;
1183
+ });
1184
+ screens.processing();
1185
+ }),
1186
+ vi.fn(),
1187
+ {
1188
+ network: {
1189
+ config: {
1190
+ jobType: JobType.smartSelfieEnrollment,
1191
+ token: MINTED_FULL_BINDING_TOKEN,
1192
+ partnerConfig: {
1193
+ partnerId: "p",
1194
+ callbackUrl: "",
1195
+ useSandbox: true,
1196
+ },
1197
+ },
1198
+ },
1199
+ },
1200
+ repository,
1201
+ );
1202
+
1203
+ expect(manager.currentScreen?.kind).toBe("capture");
1204
+ expect(manager.navigationPath.some((step) => step.kind === "consent")).toBe(
1205
+ false,
1206
+ );
1207
+
1208
+ manager.setCapturedSelfie("file:///selfie.jpg", ["file:///l1.jpg"]);
1209
+ manager.beginSubmissionIfNeeded();
1210
+
1211
+ expect(repository.received).toHaveLength(1);
1212
+ const request = repository.received[0]!;
1213
+ expect(request.userDetails).toBeUndefined();
1214
+ expect(request.consent).toBeUndefined();
1215
+ });
1216
+
1217
+ it("the minted names-and-email token reduces builder details to the unbound phone number", () => {
1218
+ const { repository } = assemble(MINTED_NAMES_AND_EMAIL_TOKEN, {
1219
+ userDetails: SAMPLE_USER_DETAILS,
1220
+ });
1221
+ const request = repository.received[0]!;
1222
+ expect(request.userDetails).toEqual({ phoneNumber: "+2348000000000" });
1223
+ expect(request.consent).toBeUndefined();
1224
+ });
1225
+
1226
+ it("regression: without a payload claim the request carries userDetails and the placeholder consent", () => {
1227
+ const legacyToken = makeJwt({ sdk_config: { livenessImageCount: 5 } });
1228
+ const { repository } = assemble(legacyToken, {
1229
+ userDetails: SAMPLE_USER_DETAILS,
1230
+ });
1231
+ const request = repository.received[0]!;
1232
+ expect(request.userDetails).toEqual(SAMPLE_USER_DETAILS);
1233
+ expect(request.tokenPayload).toBeUndefined();
1234
+ expect(request.consent).toEqual({
1235
+ granted: false,
1236
+ grantedAt: "",
1237
+ noticeLanguage: "",
1238
+ noticePrivacyPolicyUrl: "",
1239
+ });
1240
+ });
1241
+
1242
+ it("allowOfflineMode skips the token decode — strict legacy assembly applies", async () => {
1243
+ const { repository, manager } = assemble(fullBindingToken, {
1244
+ config: {
1245
+ enableCrashReporting: false,
1246
+ enableDebugMode: false,
1247
+ allowOfflineMode: true,
1248
+ globalPrivacyControl: GlobalPrivacyControl.OptOut,
1249
+ },
1250
+ });
1251
+ await flushMicrotasks();
1252
+ // Token binds everything, but offline mode never reads it — the
1253
+ // missing builder userDetails still fails assembly.
1254
+ expect(repository.received).toHaveLength(0);
1255
+ const state = manager.jobSubmissionState;
1256
+ expect(state.kind).toBe("failure");
1257
+ if (state.kind === "failure") {
1258
+ expect(state.error).toBeInstanceOf(MissingRequiredFieldException);
1259
+ }
1260
+ });
1261
+ });
1262
+
892
1263
  describe("FlowNavigationManager — typed missing-field validation (parity)", () => {
893
1264
  // Drives a submission to its failure state and returns the surfaced
894
1265
  // error so we can assert the typed `MissingRequiredFieldException`