@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
@@ -4,7 +4,9 @@ import type {
4
4
  PartnerConfig,
5
5
  } from "../../core/models/network_config";
6
6
  import { SmileIDUrls } from "../smile_id_urls";
7
- import type { ConsentInformation, UserDetails } from "../../core/models/params";
7
+ import type { ConsentInformation } from "../../core/models/params";
8
+ import { userDetailsPayloadToWire } from "../model/user_details_payload";
9
+ import type { UserDetailsPayload } from "../model/user_details_payload";
8
10
  import type {
9
11
  BiometricKycRequest,
10
12
  DocumentVerificationRequest,
@@ -87,11 +89,7 @@ export class SmileIDAPI {
87
89
  request: SmartSelfieEnrollmentRequest,
88
90
  ): Promise<JobSubmissionResponse> {
89
91
  const formData = this._buildSelfieFormData(request);
90
- appendJsonPart(
91
- formData,
92
- "user_details",
93
- userDetailsToWire(request.userDetails),
94
- );
92
+ appendUserDetailsPart(formData, request);
95
93
  this._appendPartnerConfigParts(formData);
96
94
  this._appendConsentAndMetadata(formData, request.consent, request.metadata);
97
95
  return this._postMultipart("/v3/registration", formData);
@@ -108,11 +106,7 @@ export class SmileIDAPI {
108
106
  ): Promise<JobSubmissionResponse> {
109
107
  const formData = this._buildSelfieFormData(request);
110
108
  appendStringPart(formData, "user_id", request.userId);
111
- appendJsonPart(
112
- formData,
113
- "user_details",
114
- userDetailsToWire(request.userDetails),
115
- );
109
+ appendUserDetailsPart(formData, request);
116
110
  this._appendPartnerConfigParts(formData);
117
111
  this._appendConsentAndMetadata(formData, request.consent, request.metadata);
118
112
  return this._postMultipart("/v3/authentication", formData);
@@ -126,11 +120,7 @@ export class SmileIDAPI {
126
120
  // multipart/form-data`.
127
121
  const formData = new FormData();
128
122
  this._appendIdFields(formData, request.params);
129
- appendJsonPart(
130
- formData,
131
- "user_details",
132
- userDetailsToWire(request.userDetails),
133
- );
123
+ appendUserDetailsPart(formData, request);
134
124
  if (request.params.bankCode) {
135
125
  appendStringPart(formData, "bank_code", request.params.bankCode);
136
126
  }
@@ -149,11 +139,7 @@ export class SmileIDAPI {
149
139
  const useEnrolled = request.params.useEnrolledImage;
150
140
  const formData = this._buildSelfieFormData(request);
151
141
  this._appendIdFields(formData, request.params);
152
- appendJsonPart(
153
- formData,
154
- "user_details",
155
- userDetailsToWire(request.userDetails),
156
- );
142
+ appendUserDetailsPart(formData, request);
157
143
  if (useEnrolled) {
158
144
  appendStringPart(formData, "use_enrolled_image", "true");
159
145
  }
@@ -194,11 +180,7 @@ export class SmileIDAPI {
194
180
  if (request.idType) {
195
181
  appendStringPart(formData, "id_type", request.idType);
196
182
  }
197
- appendJsonPart(
198
- formData,
199
- "user_details",
200
- userDetailsToWire(request.userDetails),
201
- );
183
+ appendUserDetailsPart(formData, request);
202
184
  this._appendPartnerConfigParts(formData);
203
185
  this._appendConsentAndMetadata(formData, request.consent, request.metadata);
204
186
  return this._postMultipart(url, formData);
@@ -306,6 +288,10 @@ export class SmileIDAPI {
306
288
  * the part with `Content-Type: application/json` causes the parser
307
289
  * to reject the part as "missing or invalid".
308
290
  *
291
+ * `consent` is `undefined` only when the token binds complete
292
+ * consent and no local source exists — the part is then omitted
293
+ * entirely and the server injects the token's consent.
294
+ *
309
295
  * The `metadata` snapshot is built by the request-construction site
310
296
  * (the flow navigation manager) and travels on the request — this
311
297
  * method serializes the snapshot it is handed rather than generating
@@ -313,19 +299,21 @@ export class SmileIDAPI {
313
299
  */
314
300
  private _appendConsentAndMetadata(
315
301
  formData: FormData,
316
- consent: ConsentInformation,
302
+ consent: ConsentInformation | undefined,
317
303
  metadata: UseSmileIDMetadata,
318
304
  ): void {
319
305
  // Transform the JS camelCase shape into the canonical snake_case
320
306
  // wire JSON before serialization. The server validates the wire
321
307
  // shape exactly — `consent.granted` (snake_case via `granted`
322
308
  // already) and `consent.granted_at` are required.
323
- appendJsonPart(formData, "consent", {
324
- granted: consent.granted,
325
- granted_at: consent.grantedAt,
326
- notice_language: consent.noticeLanguage,
327
- notice_privacy_policy_url: consent.noticePrivacyPolicyUrl,
328
- });
309
+ if (consent !== undefined) {
310
+ appendJsonPart(formData, "consent", {
311
+ granted: consent.granted,
312
+ granted_at: consent.grantedAt,
313
+ notice_language: consent.noticeLanguage,
314
+ notice_privacy_policy_url: consent.noticePrivacyPolicyUrl,
315
+ });
316
+ }
329
317
  appendJsonPart(formData, "metadata", metadata.toEntries());
330
318
  }
331
319
 
@@ -386,23 +374,22 @@ function mapJobSubmissionResponse(data: unknown): JobSubmissionResponse {
386
374
  }
387
375
 
388
376
  /**
389
- * Convert `UserDetails` to the snake_case wire shape. Optional fields
390
- * are trimmed; blank values are omitted.
377
+ * Append the `user_details` JSON multipart part. The payload arrives
378
+ * pre-reduced from assembly (token-bound fields already dropped); the
379
+ * part is omitted entirely when nothing remains — or when `userDetails`
380
+ * was legitimately omitted because the token binds every required field.
391
381
  */
392
- function userDetailsToWire(details: UserDetails): Record<string, string> {
393
- const wire: Record<string, string> = {
394
- given_names: details.givenNames,
395
- last_name: details.lastName,
396
- };
397
- const trimmedEmail = details.email?.trim();
398
- if (trimmedEmail) {
399
- wire["email"] = trimmedEmail;
400
- }
401
- const trimmedPhone = details.phoneNumber?.trim();
402
- if (trimmedPhone) {
403
- wire["phone_number"] = trimmedPhone;
404
- }
405
- return wire;
382
+ function appendUserDetailsPart(
383
+ formData: FormData,
384
+ request: {
385
+ readonly userDetails?: UserDetailsPayload;
386
+ },
387
+ ): void {
388
+ const details = request.userDetails;
389
+ if (details === undefined) return;
390
+ const wire = userDetailsPayloadToWire(details);
391
+ if (Object.keys(wire).length === 0) return;
392
+ appendJsonPart(formData, "user_details", wire);
406
393
  }
407
394
 
408
395
  /**
@@ -125,7 +125,9 @@ describe("PayloadCompressor with real JPEG bytes", () => {
125
125
 
126
126
  it(
127
127
  "compresses an inflated selfie+liveness bundle under the caps and keeps files decodable",
128
- { timeout: 120_000 },
128
+ // Real JPEG (re)compression is CPU-heavy; under v8 coverage instrumentation on CI it runs
129
+ // ~2.5x slower (~120s), so allow generous headroom over the ~120s per-test CI runtime.
130
+ { timeout: 300_000 },
129
131
  async () => {
130
132
  const selfieUri = "file:///capture/selfie.jpg";
131
133
  const livenessUri = "file:///capture/liveness.jpg";
@@ -172,7 +174,9 @@ describe("PayloadCompressor with real JPEG bytes", () => {
172
174
 
173
175
  it(
174
176
  "compresses a document-heavy bundle back-first and lands under the guarantee caps",
175
- { timeout: 120_000 },
177
+ // Real JPEG (re)compression is CPU-heavy; under v8 coverage instrumentation on CI it runs
178
+ // ~2.5x slower (~120s), so allow generous headroom over the ~120s per-test CI runtime.
179
+ { timeout: 300_000 },
176
180
  async () => {
177
181
  const frontUri = "file:///capture/front.jpg";
178
182
  const backUri = "file:///capture/back.jpg";
@@ -0,0 +1,157 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { UseSmileIDNetworkBuilder } from "../use_smile_id_network_builder";
4
+ import { JobType } from "../../../core/models/job_type";
5
+ import {
6
+ LogLevel,
7
+ RetryStrategy,
8
+ type Interceptor,
9
+ } from "../../../core/models/network_config";
10
+
11
+ /**
12
+ * The network DSL's top level: every block is lazily constructed, a
13
+ * repeated block keeps mutating the same receiver, and `build()` emits
14
+ * only the blocks the partner actually opened — an eagerly-created
15
+ * builder would ship defaults the partner never asked for.
16
+ */
17
+
18
+ describe("UseSmileIDNetworkBuilder", () => {
19
+ it("emits an empty config when no block is opened", () => {
20
+ expect(new UseSmileIDNetworkBuilder().build()).toEqual({});
21
+ });
22
+
23
+ it("freezes the built configuration", () => {
24
+ const built = new UseSmileIDNetworkBuilder().build();
25
+ expect(Object.isFrozen(built)).toBe(true);
26
+ });
27
+
28
+ it("builds the config block from the partner's identity and job type", () => {
29
+ const builder = new UseSmileIDNetworkBuilder();
30
+ builder.config((config) => {
31
+ config.jobType = JobType.enhancedKyc;
32
+ config.token = "jwt-token";
33
+ config.partnerConfig((partner) => {
34
+ partner.partnerId = "0001";
35
+ partner.useSandbox = true;
36
+ });
37
+ config.logging((logging) => {
38
+ logging.level = LogLevel.body;
39
+ });
40
+ });
41
+
42
+ const built = builder.build();
43
+ expect(built.config?.jobType).toBe(JobType.enhancedKyc);
44
+ expect(built.config?.token).toBe("jwt-token");
45
+ expect(built.config?.partnerConfig?.partnerId).toBe("0001");
46
+ expect(built.config?.partnerConfig?.useSandbox).toBe(true);
47
+ expect(built.config?.logging?.level).toBe(LogLevel.body);
48
+ });
49
+
50
+ it("builds per-phase timeouts", () => {
51
+ const builder = new UseSmileIDNetworkBuilder();
52
+ builder.timeouts((timeouts) => {
53
+ timeouts.connect = 5;
54
+ timeouts.read = 10;
55
+ timeouts.write = 15;
56
+ timeouts.call = 20;
57
+ });
58
+
59
+ expect(builder.build().timeouts).toEqual({
60
+ connect: 5,
61
+ read: 10,
62
+ write: 15,
63
+ call: 20,
64
+ });
65
+ });
66
+
67
+ it("builds the cache block, including the megabytes helper", () => {
68
+ const builder = new UseSmileIDNetworkBuilder();
69
+ builder.cache((cache) => {
70
+ cache.enabled = false;
71
+ cache.maxSize = cache.megabytes(8);
72
+ });
73
+
74
+ expect(builder.build().cache).toEqual({
75
+ enabled: false,
76
+ maxSize: 8 * 1024 * 1024,
77
+ });
78
+ });
79
+
80
+ it("builds the retry block", () => {
81
+ const builder = new UseSmileIDNetworkBuilder();
82
+ builder.retry((retry) => {
83
+ retry.strategy = RetryStrategy.exponential;
84
+ retry.maxAttempts = 4;
85
+ retry.initialDelay = 2;
86
+ });
87
+
88
+ const retry = builder.build().retry;
89
+ expect(retry?.strategy).toBe(RetryStrategy.exponential);
90
+ expect(retry?.maxAttempts).toBe(4);
91
+ expect(retry?.initialDelay).toBe(2);
92
+ });
93
+
94
+ it("builds the interceptor chain in registration order", () => {
95
+ const custom: Interceptor = { attach: vi.fn() };
96
+ const builder = new UseSmileIDNetworkBuilder();
97
+ builder.interceptors((interceptors) => {
98
+ interceptors.add(interceptors.gzip());
99
+ interceptors.add(custom);
100
+ });
101
+
102
+ const chain = builder.build().interceptors;
103
+ expect(chain).toHaveLength(2);
104
+ expect(chain?.[1]).toBe(custom);
105
+ });
106
+
107
+ it("reuses each block's builder across repeated calls", () => {
108
+ const builder = new UseSmileIDNetworkBuilder();
109
+ const seen: unknown[] = [];
110
+
111
+ builder.cache((cache) => {
112
+ seen.push(cache);
113
+ cache.maxSize = 1;
114
+ });
115
+ builder.cache((cache) => {
116
+ seen.push(cache);
117
+ cache.enabled = false;
118
+ });
119
+ builder.retry((retry) => {
120
+ seen.push(retry);
121
+ retry.strategy = RetryStrategy.fixed;
122
+ });
123
+ builder.retry((retry) => {
124
+ seen.push(retry);
125
+ retry.fixedDelay = 3;
126
+ });
127
+ builder.interceptors((interceptors) => {
128
+ seen.push(interceptors);
129
+ interceptors.add({ attach: vi.fn() });
130
+ });
131
+ builder.interceptors((interceptors) => {
132
+ seen.push(interceptors);
133
+ interceptors.add({ attach: vi.fn() });
134
+ });
135
+
136
+ expect(seen[0]).toBe(seen[1]);
137
+ expect(seen[2]).toBe(seen[3]);
138
+ expect(seen[4]).toBe(seen[5]);
139
+
140
+ const built = builder.build();
141
+ // Both mutations survived because the receiver was reused.
142
+ expect(built.cache).toEqual({ enabled: false, maxSize: 1 });
143
+ expect(built.retry?.strategy).toBe(RetryStrategy.fixed);
144
+ expect(built.retry?.fixedDelay).toBe(3);
145
+ expect(built.interceptors).toHaveLength(2);
146
+ });
147
+
148
+ it("omits blocks the partner never opened", () => {
149
+ const builder = new UseSmileIDNetworkBuilder();
150
+ builder.cache((cache) => {
151
+ cache.enabled = false;
152
+ });
153
+
154
+ const built = builder.build();
155
+ expect(Object.keys(built)).toEqual(["cache"]);
156
+ });
157
+ });
@@ -0,0 +1,62 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { CacheConfigBuilder } from "../cache_config_builder";
4
+ import { RetryConfigBuilder, RetryStrategy } from "../retry_config_builder";
5
+
6
+ /** Retry/cache DSL builders: pin the documented defaults, frozen output, and overrides. */
7
+ describe("RetryConfigBuilder", () => {
8
+ it("builds a frozen config with the documented defaults", () => {
9
+ const config = new RetryConfigBuilder().build();
10
+
11
+ expect(config).toEqual({
12
+ maxAttempts: 1,
13
+ strategy: RetryStrategy.none,
14
+ initialDelay: 1,
15
+ multiplier: 2,
16
+ maxDelay: 32,
17
+ jitter: 0.2,
18
+ fixedDelay: 1,
19
+ });
20
+ expect(Object.isFrozen(config)).toBe(true);
21
+ });
22
+
23
+ it("forces maxAttempts to 1 for none, whatever the field says", () => {
24
+ const builder = new RetryConfigBuilder();
25
+ builder.maxAttempts = 5;
26
+
27
+ expect(builder.build().maxAttempts).toBe(1);
28
+ });
29
+
30
+ it("applies the attempt budget once a strategy opts in", () => {
31
+ const builder = new RetryConfigBuilder();
32
+ builder.strategy = RetryStrategy.exponential;
33
+
34
+ expect(builder.build().maxAttempts).toBe(3);
35
+ });
36
+
37
+ it("carries overridden fields through build()", () => {
38
+ const builder = new RetryConfigBuilder();
39
+ builder.maxAttempts = 5;
40
+ builder.strategy = RetryStrategy.fixed;
41
+ builder.fixedDelay = 4;
42
+
43
+ const config = builder.build();
44
+
45
+ expect(config.maxAttempts).toBe(5);
46
+ expect(config.strategy).toBe(RetryStrategy.fixed);
47
+ expect(config.fixedDelay).toBe(4);
48
+ });
49
+ });
50
+
51
+ describe("CacheConfigBuilder", () => {
52
+ it("defaults to enabled with a 50 MB ceiling", () => {
53
+ const config = new CacheConfigBuilder().build();
54
+
55
+ expect(config).toEqual({ enabled: true, maxSize: 50 * 1024 * 1024 });
56
+ expect(Object.isFrozen(config)).toBe(true);
57
+ });
58
+
59
+ it("megabytes() converts MB to bytes", () => {
60
+ expect(new CacheConfigBuilder().megabytes(10)).toBe(10 * 1024 * 1024);
61
+ });
62
+ });
@@ -8,16 +8,16 @@ export { RetryStrategy };
8
8
  /**
9
9
  * DSL builder for request retry behaviour.
10
10
  *
11
- * Defaults to exponential backoff with 3 attempts, 1 s initial delay,
12
- * 32 s ceiling, ±20% jitter. `fixedDelay` is honoured only when
13
- * `strategy = RetryStrategy.fixed`.
11
+ * Defaults to no retry. Set `strategy` to opt in, and the remaining fields
12
+ * then apply: 3 attempts, 1 s initial delay, 32 s ceiling, ±20% jitter.
13
+ * `fixedDelay` is honoured only when `strategy = RetryStrategy.fixed`.
14
14
  */
15
15
  export class RetryConfigBuilder {
16
- /** Maximum number of attempts per request, including the first. */
16
+ /** Attempts per request including the first; applies once `strategy` opts in. */
17
17
  maxAttempts = 3;
18
18
 
19
19
  /** Backoff strategy between attempts: exponential, fixed, or none. */
20
- strategy: RetryStrategy = RetryStrategy.exponential;
20
+ strategy: RetryStrategy = RetryStrategy.none;
21
21
 
22
22
  /** Delay before the first retry, in seconds (exponential strategy). */
23
23
  initialDelay = 1;
@@ -45,7 +45,7 @@ export class RetryConfigBuilder {
45
45
 
46
46
  build(): RetryConfig {
47
47
  return Object.freeze({
48
- maxAttempts: this.maxAttempts,
48
+ maxAttempts: this.strategy === RetryStrategy.none ? 1 : this.maxAttempts,
49
49
  strategy: this.strategy,
50
50
  initialDelay: this.initialDelay,
51
51
  multiplier: this.multiplier,
@@ -0,0 +1,163 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ AxiosError,
4
+ type AxiosResponse,
5
+ type InternalAxiosRequestConfig,
6
+ } from "axios";
7
+
8
+ import { JobType } from "../../../core/models/job_type";
9
+ import type { NetworkConfig } from "../../../core/models/network_config";
10
+ import { makeNetworkingFactory } from "../networking_factory";
11
+ import { SmileIDCryptoManager } from "../../security/smile_id_crypto_manager";
12
+ import {
13
+ REQUEST_MAC_HEADER,
14
+ REQUEST_MAC_OPT_IN_KEY,
15
+ REQUEST_TIMESTAMP_HEADER,
16
+ } from "../../api/interceptors/smile_request_mac_interceptor";
17
+
18
+ /**
19
+ * Standing-guard composition test for token-refresh × gzip × request-MAC on a
20
+ * 401 → refresh → retry. `TokenRefreshInterceptor` re-issues the config, re-running the whole
21
+ * chain (auth re-stamps the token, MAC re-signs, gzip re-stamps `Accept-Encoding`). If a future
22
+ * change made gzip replace `config.data` with compressed bytes, the MAC would sign a body the
23
+ * canonicalizer no longer sees as FormData → `{}`. Pins the retried MAC against the uncompressed
24
+ * body so that regression fails loud.
25
+ */
26
+ describe("token-refresh x gzip x request-MAC composition", () => {
27
+ const initialToken = "token-1";
28
+ const refreshedToken = "token-2";
29
+ const signingSecret = "test-signing-secret";
30
+ const cryptoManager = new SmileIDCryptoManager(signingSecret);
31
+
32
+ type FormDataPart = { fieldName: string; string?: string; uri?: string };
33
+
34
+ /**
35
+ * Stand-in for RN's `FormData`. Besides `getParts()` (what the canonicalizer reads), it
36
+ * satisfies axios's `isFormData()` (`Symbol.toStringTag` + `append`) so axios's request
37
+ * transform leaves it untouched instead of JSON-stringifying it.
38
+ */
39
+ class FakeFormData {
40
+ constructor(private readonly parts: readonly FormDataPart[]) {}
41
+ get [Symbol.toStringTag](): string {
42
+ return "FormData";
43
+ }
44
+ append(): void {
45
+ /* not exercised — only presence is checked by axios's isFormData() */
46
+ }
47
+ getParts(): readonly FormDataPart[] {
48
+ return this.parts;
49
+ }
50
+ }
51
+
52
+ function submissionBody(): FakeFormData {
53
+ return new FakeFormData([
54
+ { fieldName: "country", string: "NG" },
55
+ { fieldName: "partner_params", string: '{"flow":"enhanced"}' },
56
+ {
57
+ fieldName: "selfie_image",
58
+ uri: "data:application/octet-stream;base64,c2VsZmll",
59
+ },
60
+ ]);
61
+ }
62
+
63
+ /** Flattens the observed AxiosHeaders to a lower-cased record, matching how
64
+ * RequestCanonicalizer lower-cases header names before signing. */
65
+ function headersRecord(
66
+ headers: InternalAxiosRequestConfig["headers"],
67
+ ): Record<string, string> {
68
+ const out: Record<string, string> = {};
69
+ for (const [key, value] of Object.entries(
70
+ headers as unknown as Record<string, unknown>,
71
+ )) {
72
+ if (value != null) out[key.toLowerCase()] = String(value);
73
+ }
74
+ return out;
75
+ }
76
+
77
+ function makeConfig(): NetworkConfig {
78
+ return {
79
+ config: {
80
+ jobType: JobType.smartSelfieEnrollment,
81
+ token: initialToken,
82
+ onTokenExpired: async () => refreshedToken,
83
+ partnerConfig: {
84
+ partnerId: "partner-1",
85
+ callbackUrl: "https://partner.example.com/cb",
86
+ useSandbox: true,
87
+ },
88
+ },
89
+ };
90
+ }
91
+
92
+ it("signs the refresh-retried request's MAC over the uncompressed submission body", async () => {
93
+ const requestsSeen: InternalAxiosRequestConfig[] = [];
94
+ const { client } = makeNetworkingFactory(makeConfig(), () => signingSecret);
95
+
96
+ // 401 first, 200 on the retry. A custom adapter bypasses axios's validateStatus, so it
97
+ // must reject non-2xx itself for TokenRefreshInterceptor's error handler to see it.
98
+ client.defaults.adapter = (
99
+ requestConfig: InternalAxiosRequestConfig,
100
+ ): Promise<AxiosResponse> => {
101
+ requestsSeen.push(requestConfig);
102
+ const isFirstCall = requestsSeen.length === 1;
103
+ const response: AxiosResponse = {
104
+ status: isFirstCall ? 401 : 200,
105
+ statusText: isFirstCall ? "Unauthorized" : "OK",
106
+ data: isFirstCall ? { error: "unauthorized" } : { jobId: "job-1" },
107
+ headers: {},
108
+ config: requestConfig,
109
+ };
110
+ if (isFirstCall) {
111
+ return Promise.reject(
112
+ new AxiosError(
113
+ "Request failed with status code 401",
114
+ AxiosError.ERR_BAD_REQUEST,
115
+ requestConfig,
116
+ undefined,
117
+ response,
118
+ ),
119
+ );
120
+ }
121
+ return Promise.resolve(response);
122
+ };
123
+
124
+ const body = submissionBody();
125
+ const response = await client.post("/v3/enhanced_kyc", body, {
126
+ [REQUEST_MAC_OPT_IN_KEY]: true,
127
+ } as Partial<InternalAxiosRequestConfig>);
128
+
129
+ expect(response.status).toBe(200);
130
+ expect(requestsSeen).toHaveLength(2);
131
+
132
+ const retried = requestsSeen[1]!;
133
+ const retriedHeaders = headersRecord(retried.headers);
134
+
135
+ expect(retriedHeaders["smileid-token"]).toBe(refreshedToken);
136
+ expect(retriedHeaders["accept-encoding"]).toBe("gzip");
137
+ // gzip is hint-only — it never compresses the body, so no Content-Encoding claim.
138
+ expect(retriedHeaders["content-encoding"]).toBeUndefined();
139
+
140
+ const retriedMac = retriedHeaders[REQUEST_MAC_HEADER];
141
+ const retriedTimestamp = retriedHeaders[REQUEST_TIMESTAMP_HEADER];
142
+ expect(retriedMac).toBeDefined();
143
+ expect(retriedTimestamp).toBeDefined();
144
+
145
+ // Recompute the server-verified MAC over the retried headers and the real body — gzip
146
+ // is hint-only (never replaces config.data), so `body` is the bytes that shipped.
147
+ const expectedMac = await cryptoManager.sign(
148
+ retriedTimestamp!,
149
+ retriedHeaders,
150
+ body,
151
+ );
152
+ expect(retriedMac).toBe(expectedMac);
153
+
154
+ // And it must not match a MAC computed against an empty body — the
155
+ // signature of the regression this test guards against.
156
+ const emptyBodyMac = await cryptoManager.sign(
157
+ retriedTimestamp!,
158
+ retriedHeaders,
159
+ undefined,
160
+ );
161
+ expect(retriedMac).not.toBe(emptyBodyMac);
162
+ });
163
+ });
@@ -36,7 +36,7 @@ function makeBaseConfig(): NetworkConfig {
36
36
  }
37
37
 
38
38
  const metadata = new UseSmileIDMetadata({
39
- sdkVersion: "12.0.0",
39
+ sdkVersion: "12.0.2",
40
40
  sdk: "react-native-expo",
41
41
  deviceOs: "",
42
42
  deviceModel: "",