@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
@@ -34,7 +34,7 @@ const CONSENT: ConsentInformation = Object.freeze({
34
34
  });
35
35
 
36
36
  const METADATA = new UseSmileIDMetadata({
37
- sdkVersion: "12.0.0",
37
+ sdkVersion: "12.0.2",
38
38
  sdk: "react-native-expo",
39
39
  deviceOs: "",
40
40
  deviceModel: "",
@@ -0,0 +1,366 @@
1
+ import { describe, expect, it, beforeEach, vi } from "vitest";
2
+
3
+ // `vi.mock` is hoisted above the imports, so anything its factory closes over
4
+ // must be created with `vi.hoisted` or it is still in the TDZ when the factory
5
+ // runs.
6
+ const { compress } = vi.hoisted(() => ({ compress: vi.fn() }));
7
+
8
+ vi.mock("axios", () => ({
9
+ __esModule: true,
10
+ default: { create: vi.fn(), isAxiosError: () => false },
11
+ isAxiosError: () => false,
12
+ }));
13
+
14
+ // The compression runner touches `expo-file-system` and the native re-encoder;
15
+ // the repository only cares that it produces (or fails to produce) size hints.
16
+ vi.mock("../../compression/payload_compression_runner", () => ({
17
+ PayloadCompressionRunner: { compress },
18
+ }));
19
+
20
+ import { UseSmileIDRepositoryImpl } from "../use_smile_id_repository_impl";
21
+ import {
22
+ RetryStrategy,
23
+ type RetryConfig,
24
+ } from "../../../core/models/network_config";
25
+ import { JobType } from "../../../core/models/job_type";
26
+ import { UseSmileIDMetadata } from "../../../metadata/use_smile_id_metadata";
27
+ import { UseSmileIDValidationException } from "../../../core/exception/use_smile_id_validation_exception";
28
+ import { UseSmileIDNetworkException } from "../../../core/exception/use_smile_id_network_exception";
29
+ import type { JobRequest } from "../../model/job_request";
30
+ import type { JobSubmissionResponse } from "../../model/job_submission_response";
31
+ import type { ConsentInformation } from "../../../core/models/params";
32
+
33
+ /**
34
+ * Repository dispatch + pre-flight compression tests: every job type must
35
+ * reach its matching API method, the compression sizes must land on the
36
+ * submitted metadata, and a compression failure must never block the
37
+ * submission. The BVN short-circuit lives in `use_smile_id_repository.test.ts`.
38
+ */
39
+
40
+ const NO_RETRY: RetryConfig = Object.freeze({
41
+ maxAttempts: 1,
42
+ strategy: RetryStrategy.none,
43
+ initialDelay: 0,
44
+ multiplier: 1,
45
+ maxDelay: 0,
46
+ jitter: 0,
47
+ fixedDelay: 0,
48
+ });
49
+
50
+ const CONSENT: ConsentInformation = Object.freeze({
51
+ granted: true,
52
+ grantedAt: "2026-04-30T00:00:00.000Z",
53
+ noticeLanguage: "EN",
54
+ noticePrivacyPolicyUrl: "https://example.test/p",
55
+ });
56
+
57
+ const METADATA = new UseSmileIDMetadata({
58
+ sdkVersion: "12.0.2",
59
+ sdk: "react-native-expo",
60
+ deviceOs: "",
61
+ deviceModel: "",
62
+ appName: "",
63
+ sessionId: "test-session-id",
64
+ clientIp: "",
65
+ networkConnection: "",
66
+ carrierInfo: "",
67
+ timezone: "",
68
+ locale: "",
69
+ screenResolution: "",
70
+ memoryInfoMb: 0,
71
+ deviceOrientation: "",
72
+ systemArchitecture: "",
73
+ numberOfCameras: 0,
74
+ hostApplication: "",
75
+ securityPolicyVersion: "12.0.0",
76
+ sdkConfigSource: "default",
77
+ imageWidth: 0,
78
+ imageHeight: 0,
79
+ livenessImageWidth: 0,
80
+ livenessImageHeight: 0,
81
+ });
82
+
83
+ const RESPONSE: JobSubmissionResponse = {
84
+ jobId: "job-1",
85
+ userId: "user-1",
86
+ status: "approved",
87
+ message: "ok",
88
+ };
89
+
90
+ const USER_DETAILS = {
91
+ givenNames: "A",
92
+ lastName: "B",
93
+ email: "a@b.test",
94
+ } as const;
95
+
96
+ type StubApi = Record<string, ReturnType<typeof vi.fn>>;
97
+
98
+ function makeApi(): StubApi {
99
+ return {
100
+ enhancedKyc: vi.fn(async () => RESPONSE),
101
+ biometricKyc: vi.fn(async () => RESPONSE),
102
+ smartSelfieEnrollment: vi.fn(async () => RESPONSE),
103
+ smartSelfieAuthentication: vi.fn(async () => RESPONSE),
104
+ documentVerification: vi.fn(async () => RESPONSE),
105
+ enhancedDocumentVerification: vi.fn(async () => RESPONSE),
106
+ };
107
+ }
108
+
109
+ function makeRepository(api: StubApi, retry: RetryConfig = NO_RETRY) {
110
+ return new UseSmileIDRepositoryImpl(
111
+ api as unknown as ConstructorParameters<typeof UseSmileIDRepositoryImpl>[0],
112
+ retry,
113
+ );
114
+ }
115
+
116
+ const selfieEnrollment: JobRequest = {
117
+ jobType: JobType.smartSelfieEnrollment,
118
+ selfieImage: "file:///selfie.jpg",
119
+ livenessImages: ["file:///liveness-1.jpg"],
120
+ userDetails: USER_DETAILS,
121
+ enhanced: false,
122
+ expectedLivenessImageCount: 1,
123
+ consent: CONSENT,
124
+ metadata: METADATA,
125
+ };
126
+
127
+ const selfieAuthentication: JobRequest = {
128
+ jobType: JobType.smartSelfieAuthentication,
129
+ selfieImage: "file:///selfie.jpg",
130
+ livenessImages: [],
131
+ userDetails: USER_DETAILS,
132
+ userId: "user-1",
133
+ enhanced: true,
134
+ expectedLivenessImageCount: 0,
135
+ consent: CONSENT,
136
+ metadata: METADATA,
137
+ };
138
+
139
+ const biometricKyc: JobRequest = {
140
+ jobType: JobType.biometricKyc,
141
+ selfieImage: "file:///selfie.jpg",
142
+ livenessImages: [],
143
+ params: { idType: "NIN", idNumber: "1", country: "NG" },
144
+ userDetails: USER_DETAILS,
145
+ expectedLivenessImageCount: 0,
146
+ consent: CONSENT,
147
+ metadata: METADATA,
148
+ };
149
+
150
+ const enhancedKyc: JobRequest = {
151
+ jobType: JobType.enhancedKyc,
152
+ params: { idType: "NIN", idNumber: "1", country: "NG" },
153
+ userDetails: USER_DETAILS,
154
+ consent: CONSENT,
155
+ metadata: METADATA,
156
+ };
157
+
158
+ const documentVerification: JobRequest = {
159
+ jobType: JobType.documentVerification,
160
+ selfieImage: "file:///selfie.jpg",
161
+ livenessImages: [],
162
+ frontDocument: "file:///front.jpg",
163
+ backDocument: "file:///back.jpg",
164
+ country: "NG",
165
+ userDetails: USER_DETAILS,
166
+ expectedLivenessImageCount: 0,
167
+ consent: CONSENT,
168
+ metadata: METADATA,
169
+ };
170
+
171
+ const enhancedDocumentVerification: JobRequest = {
172
+ jobType: JobType.enhancedDocumentVerification,
173
+ selfieImage: "file:///selfie.jpg",
174
+ livenessImages: [],
175
+ frontDocument: "file:///front.jpg",
176
+ country: "NG",
177
+ idType: "NATIONAL_ID",
178
+ userDetails: USER_DETAILS,
179
+ expectedLivenessImageCount: 0,
180
+ consent: CONSENT,
181
+ metadata: METADATA,
182
+ };
183
+
184
+ function metadataNames(request: unknown): readonly string[] {
185
+ const { metadata } = request as { metadata: UseSmileIDMetadata };
186
+ return metadata.toEntries().map((entry) => entry.name);
187
+ }
188
+
189
+ function metadataValue(request: unknown, name: string): string | undefined {
190
+ const { metadata } = request as { metadata: UseSmileIDMetadata };
191
+ return metadata.toEntries().find((entry) => entry.name === name)?.value;
192
+ }
193
+
194
+ describe("UseSmileIDRepositoryImpl — dispatch", () => {
195
+ beforeEach(() => {
196
+ compress.mockReset().mockResolvedValue({});
197
+ });
198
+
199
+ const cases: ReadonlyArray<readonly [string, JobRequest, string]> = [
200
+ ["smart selfie enrollment", selfieEnrollment, "smartSelfieEnrollment"],
201
+ [
202
+ "smart selfie authentication",
203
+ selfieAuthentication,
204
+ "smartSelfieAuthentication",
205
+ ],
206
+ ["enhanced KYC", enhancedKyc, "enhancedKyc"],
207
+ ["biometric KYC", biometricKyc, "biometricKyc"],
208
+ ["document verification", documentVerification, "documentVerification"],
209
+ [
210
+ "enhanced document verification",
211
+ enhancedDocumentVerification,
212
+ "enhancedDocumentVerification",
213
+ ],
214
+ ];
215
+
216
+ for (const [label, request, method] of cases) {
217
+ it(`routes ${label} to api.${method}`, async () => {
218
+ const api = makeApi();
219
+ const result = await makeRepository(api).submit(request);
220
+
221
+ expect(result.status).toBe("success");
222
+ expect(api[method]).toHaveBeenCalledTimes(1);
223
+ for (const [, , other] of cases) {
224
+ if (other !== method) expect(api[other]).not.toHaveBeenCalled();
225
+ }
226
+ });
227
+ }
228
+ });
229
+
230
+ describe("UseSmileIDRepositoryImpl — pre-flight compression", () => {
231
+ beforeEach(() => {
232
+ compress.mockReset().mockResolvedValue({});
233
+ });
234
+
235
+ it("compresses the selfie bundle for selfie job types", async () => {
236
+ const api = makeApi();
237
+ await makeRepository(api).submit(selfieEnrollment);
238
+
239
+ expect(compress).toHaveBeenCalledTimes(1);
240
+ const args = compress.mock.calls[0]?.[0] as Record<string, unknown>;
241
+ expect(args.selfieImage).toBe("file:///selfie.jpg");
242
+ expect(args.livenessImages).toEqual(["file:///liveness-1.jpg"]);
243
+ expect(args.frontDocument).toBeUndefined();
244
+ expect(args.metadataBytes).toBeGreaterThan(0);
245
+ });
246
+
247
+ it("includes both document sides for document job types", async () => {
248
+ const api = makeApi();
249
+ await makeRepository(api).submit(documentVerification);
250
+
251
+ const args = compress.mock.calls[0]?.[0] as Record<string, unknown>;
252
+ expect(args.frontDocument).toBe("file:///front.jpg");
253
+ expect(args.backDocument).toBe("file:///back.jpg");
254
+ });
255
+
256
+ it("skips compression entirely for image-less job types", async () => {
257
+ const api = makeApi();
258
+ await makeRepository(api).submit(enhancedKyc);
259
+
260
+ expect(compress).not.toHaveBeenCalled();
261
+ expect(metadataNames(api.enhancedKyc.mock.calls[0]?.[0])).not.toContain(
262
+ "selfie_original_size",
263
+ );
264
+ });
265
+
266
+ it("stamps every measured size onto the submitted metadata", async () => {
267
+ compress.mockResolvedValue({
268
+ selfieOriginalMb: 1.5,
269
+ selfieFinalMb: 0.5,
270
+ frontOriginalMb: 2,
271
+ frontFinalMb: 1,
272
+ backOriginalMb: 3,
273
+ backFinalMb: 1.25,
274
+ });
275
+ const api = makeApi();
276
+ await makeRepository(api).submit(documentVerification);
277
+
278
+ const submitted = api.documentVerification.mock.calls[0]?.[0];
279
+ expect(metadataValue(submitted, "selfie_original_size")).toBe("1.5");
280
+ expect(metadataValue(submitted, "selfie_final_compressed_size")).toBe(
281
+ "0.5",
282
+ );
283
+ expect(metadataValue(submitted, "document_front_original_size")).toBe("2");
284
+ expect(
285
+ metadataValue(submitted, "document_front_final_compressed_size"),
286
+ ).toBe("1");
287
+ expect(metadataValue(submitted, "document_back_original_size")).toBe("3");
288
+ expect(
289
+ metadataValue(submitted, "document_back_final_compressed_size"),
290
+ ).toBe("1.25");
291
+ // The caller's metadata instance is never mutated.
292
+ expect(METADATA.toEntries().map((entry) => entry.name)).not.toContain(
293
+ "selfie_original_size",
294
+ );
295
+ });
296
+
297
+ it("drops absent and zero sizes rather than stamping them", async () => {
298
+ compress.mockResolvedValue({
299
+ selfieOriginalMb: 1.5,
300
+ selfieFinalMb: 0,
301
+ frontOriginalMb: undefined,
302
+ });
303
+ const api = makeApi();
304
+ await makeRepository(api).submit(documentVerification);
305
+
306
+ const submitted = api.documentVerification.mock.calls[0]?.[0];
307
+ const stamped = metadataNames(submitted);
308
+ expect(stamped).toContain("selfie_original_size");
309
+ expect(stamped).not.toContain("selfie_final_compressed_size");
310
+ expect(stamped).not.toContain("document_front_original_size");
311
+ });
312
+
313
+ it("leaves the metadata untouched when nothing was measured", async () => {
314
+ compress.mockResolvedValue({});
315
+ const api = makeApi();
316
+ await makeRepository(api).submit(selfieEnrollment);
317
+
318
+ const submitted = api.smartSelfieEnrollment.mock.calls[0]?.[0];
319
+ expect(submitted).toBe(selfieEnrollment);
320
+ });
321
+
322
+ it("submits the captured images as-is when compression throws", async () => {
323
+ compress.mockRejectedValue(new Error("native re-encoder unavailable"));
324
+ const api = makeApi();
325
+ const result = await makeRepository(api).submit(selfieEnrollment);
326
+
327
+ expect(result.status).toBe("success");
328
+ expect(api.smartSelfieEnrollment).toHaveBeenCalledTimes(1);
329
+ expect(
330
+ metadataNames(api.smartSelfieEnrollment.mock.calls[0]?.[0]),
331
+ ).not.toContain("selfie_original_size");
332
+ });
333
+ });
334
+
335
+ describe("UseSmileIDRepositoryImpl — error mapping", () => {
336
+ beforeEach(() => {
337
+ compress.mockReset().mockResolvedValue({});
338
+ });
339
+
340
+ it("surfaces a validation exception unchanged rather than as a network error", async () => {
341
+ const validationError = new UseSmileIDValidationException({
342
+ message: "liveness image count mismatch",
343
+ });
344
+ const api = makeApi();
345
+ api.smartSelfieEnrollment.mockRejectedValue(validationError);
346
+
347
+ const result = await makeRepository(api).submit(selfieEnrollment);
348
+
349
+ expect(result.status).toBe("failure");
350
+ if (result.status === "failure") {
351
+ expect(result.error).toBe(validationError);
352
+ }
353
+ });
354
+
355
+ it("maps any other thrown error into the network exception hierarchy", async () => {
356
+ const api = makeApi();
357
+ api.enhancedKyc.mockRejectedValue(new Error("socket hang up"));
358
+
359
+ const result = await makeRepository(api).submit(enhancedKyc);
360
+
361
+ expect(result.status).toBe("failure");
362
+ if (result.status === "failure") {
363
+ expect(result.error).toBeInstanceOf(UseSmileIDNetworkException);
364
+ }
365
+ });
366
+ });
@@ -51,7 +51,7 @@ const CONSENT: ConsentInformation = Object.freeze({
51
51
  // BVN path short-circuits before the API serializes it, but the field
52
52
  // is required on `BaseJobRequest` so the fixture must supply one.
53
53
  const METADATA = new UseSmileIDMetadata({
54
- sdkVersion: "12.0.0",
54
+ sdkVersion: "12.0.2",
55
55
  sdk: "react-native-expo",
56
56
  deviceOs: "",
57
57
  deviceModel: "",
@@ -0,0 +1,132 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { RequestCanonicalizer } from "../request_canonicalizer";
4
+ import { SmileIDCryptoManager } from "../smile_id_crypto_manager";
5
+
6
+ /**
7
+ * Golden-vector suite for `RequestCanonicalizer`: pins the exact canonical bytes and the resulting
8
+ * `smileid-request-mac` for a fixed key across the shapes `canonicalize()` distinguishes. This
9
+ * SDK's own contract with the server, not a cross-SDK guarantee.
10
+ */
11
+ const TIMESTAMP = "1700000000000";
12
+ const SIGNING_SECRET = "golden-vector-secret";
13
+ const cryptoManager = new SmileIDCryptoManager(SIGNING_SECRET);
14
+
15
+ const BASE_HEADERS = {
16
+ "smileid-token": "golden-token",
17
+ "smileid-request-timestamp": TIMESTAMP,
18
+ };
19
+
20
+ type FormDataPart = { fieldName: string; string?: string; uri?: string };
21
+
22
+ /** Minimal stand-in for React Native's `FormData` (only `getParts` is read). */
23
+ function formData(parts: readonly FormDataPart[]) {
24
+ return { getParts: () => parts };
25
+ }
26
+
27
+ function decode(bytes: Uint8Array): string {
28
+ return Buffer.from(bytes).toString("utf-8");
29
+ }
30
+
31
+ const MULTIPART_BODY = formData([
32
+ { fieldName: "country", string: "NG" },
33
+ { fieldName: "allow_new_enroll", string: "true" },
34
+ { fieldName: "partner_params", string: '{"job_id":"j1"}' },
35
+ {
36
+ fieldName: "selfie_image",
37
+ uri: "data:application/octet-stream;base64,c2VsZmll",
38
+ },
39
+ ]);
40
+
41
+ // Same logical fields as MULTIPART_BODY, in reverse part order.
42
+ const MULTIPART_BODY_REORDERED = formData([
43
+ {
44
+ fieldName: "selfie_image",
45
+ uri: "data:application/octet-stream;base64,c2VsZmll",
46
+ },
47
+ { fieldName: "partner_params", string: '{"job_id":"j1"}' },
48
+ { fieldName: "allow_new_enroll", string: "true" },
49
+ { fieldName: "country", string: "NG" },
50
+ ]);
51
+
52
+ const MIXED_CASE_HEADERS = {
53
+ "Smileid-Token": "golden-token",
54
+ "SMILEID-REQUEST-TIMESTAMP": TIMESTAMP,
55
+ };
56
+
57
+ describe("RequestCanonicalizer golden vectors", () => {
58
+ it("(a) canonicalizes a bodyless request to the signed headers + `{}`", async () => {
59
+ const bytes = await RequestCanonicalizer.canonicalize(
60
+ BASE_HEADERS,
61
+ undefined,
62
+ );
63
+ expect(decode(bytes)).toBe(
64
+ '{"smileid-request-timestamp":"1700000000000","smileid-token":"golden-token"}{}',
65
+ );
66
+ expect(await cryptoManager.sign(TIMESTAMP, BASE_HEADERS, undefined)).toBe(
67
+ "qikeRL0jNa7bZKUZdoN7tY30VvVy4ol/hcMdyg4PU4w=",
68
+ );
69
+ });
70
+
71
+ it("(b) canonicalizes a raw JSON-string body the same as a bodyless request — only FormData-shaped bodies are reconstructed", async () => {
72
+ const bytes = await RequestCanonicalizer.canonicalize(
73
+ BASE_HEADERS,
74
+ '{"hello":"world"}',
75
+ );
76
+ // Same canonical bytes as case (a): a JSON string isn't FormData-like,
77
+ // so canonicalBody() falls through to the same `{}` as no body at all.
78
+ expect(decode(bytes)).toBe(
79
+ '{"smileid-request-timestamp":"1700000000000","smileid-token":"golden-token"}{}',
80
+ );
81
+ expect(
82
+ await cryptoManager.sign(TIMESTAMP, BASE_HEADERS, '{"hello":"world"}'),
83
+ ).toBe("qikeRL0jNa7bZKUZdoN7tY30VvVy4ol/hcMdyg4PU4w=");
84
+ });
85
+
86
+ it("(c) canonicalizes a multipart FormData body: scalar, boolean-coerced, JSON, and file parts", async () => {
87
+ const bytes = await RequestCanonicalizer.canonicalize(
88
+ BASE_HEADERS,
89
+ MULTIPART_BODY,
90
+ );
91
+ expect(decode(bytes)).toBe(
92
+ '{"smileid-request-timestamp":"1700000000000","smileid-token":"golden-token"}' +
93
+ '{"allow_new_enroll":true,"country":"NG","partner_params":{"job_id":"j1"},"selfie_image":"c2VsZmll"}',
94
+ );
95
+ expect(
96
+ await cryptoManager.sign(TIMESTAMP, BASE_HEADERS, MULTIPART_BODY),
97
+ ).toBe("eNJqKR45aNiou4JM0yVRNYrTdhfg1VblWvsbrRndoLQ=");
98
+ });
99
+
100
+ it("(d) part ordering does not change the canonical bytes or the MAC", async () => {
101
+ const bytes = await RequestCanonicalizer.canonicalize(
102
+ BASE_HEADERS,
103
+ MULTIPART_BODY_REORDERED,
104
+ );
105
+ expect(decode(bytes)).toBe(
106
+ '{"smileid-request-timestamp":"1700000000000","smileid-token":"golden-token"}' +
107
+ '{"allow_new_enroll":true,"country":"NG","partner_params":{"job_id":"j1"},"selfie_image":"c2VsZmll"}',
108
+ );
109
+ expect(
110
+ await cryptoManager.sign(
111
+ TIMESTAMP,
112
+ BASE_HEADERS,
113
+ MULTIPART_BODY_REORDERED,
114
+ ),
115
+ ).toBe("eNJqKR45aNiou4JM0yVRNYrTdhfg1VblWvsbrRndoLQ=");
116
+ });
117
+
118
+ it("(e) header key casing does not change the canonical bytes or the MAC", async () => {
119
+ const bytes = await RequestCanonicalizer.canonicalize(
120
+ MIXED_CASE_HEADERS,
121
+ undefined,
122
+ );
123
+ // Identical to case (a): canonicalHeaders() lower-cases every header
124
+ // name before signing, so casing at the call site is not observable.
125
+ expect(decode(bytes)).toBe(
126
+ '{"smileid-request-timestamp":"1700000000000","smileid-token":"golden-token"}{}',
127
+ );
128
+ expect(
129
+ await cryptoManager.sign(TIMESTAMP, MIXED_CASE_HEADERS, undefined),
130
+ ).toBe("qikeRL0jNa7bZKUZdoN7tY30VvVy4ol/hcMdyg4PU4w=");
131
+ });
132
+ });