@smileid/usesmileid 12.0.0-beta01 → 12.0.0

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 (403) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +701 -89
  3. package/dist/camera/camera_defaults.d.ts +14 -9
  4. package/dist/camera/camera_defaults.d.ts.map +1 -1
  5. package/dist/camera/camera_defaults.js +11 -9
  6. package/dist/camera/camera_defaults.js.map +1 -1
  7. package/dist/camera/orientation_degrees.d.ts +5 -6
  8. package/dist/camera/orientation_degrees.d.ts.map +1 -1
  9. package/dist/camera/orientation_degrees.js +5 -6
  10. package/dist/camera/orientation_degrees.js.map +1 -1
  11. package/dist/core/models/document_capture_mode.d.ts +8 -0
  12. package/dist/core/models/document_capture_mode.d.ts.map +1 -1
  13. package/dist/core/models/document_capture_mode.js +24 -0
  14. package/dist/core/models/document_capture_mode.js.map +1 -1
  15. package/dist/core/models/document_type.d.ts +2 -2
  16. package/dist/core/models/document_type.d.ts.map +1 -1
  17. package/dist/core/models/document_type.js +6 -4
  18. package/dist/core/models/document_type.js.map +1 -1
  19. package/dist/core/models/result.d.ts +9 -8
  20. package/dist/core/models/result.d.ts.map +1 -1
  21. package/dist/core/models/result.js +5 -6
  22. package/dist/core/models/result.js.map +1 -1
  23. package/dist/data/api/interceptors/smile_auth_interceptor.js +1 -1
  24. package/dist/data/api/interceptors/smile_auth_interceptor.js.map +1 -1
  25. package/dist/data/api/interceptors/smile_integrity_interceptor.d.ts +11 -0
  26. package/dist/data/api/interceptors/smile_integrity_interceptor.d.ts.map +1 -1
  27. package/dist/data/api/interceptors/smile_integrity_interceptor.js +13 -0
  28. package/dist/data/api/interceptors/smile_integrity_interceptor.js.map +1 -1
  29. package/dist/data/api/interceptors/smile_request_mac_interceptor.d.ts +8 -0
  30. package/dist/data/api/interceptors/smile_request_mac_interceptor.d.ts.map +1 -1
  31. package/dist/data/api/interceptors/smile_request_mac_interceptor.js +11 -0
  32. package/dist/data/api/interceptors/smile_request_mac_interceptor.js.map +1 -1
  33. package/dist/data/compression/compressible_asset.d.ts +41 -0
  34. package/dist/data/compression/compressible_asset.d.ts.map +1 -0
  35. package/dist/data/compression/compressible_asset.js +34 -0
  36. package/dist/data/compression/compressible_asset.js.map +1 -0
  37. package/dist/data/compression/payload_compression_config.d.ts +36 -0
  38. package/dist/data/compression/payload_compression_config.d.ts.map +1 -0
  39. package/dist/data/compression/payload_compression_config.js +52 -0
  40. package/dist/data/compression/payload_compression_config.js.map +1 -0
  41. package/dist/data/compression/payload_compression_runner.d.ts +29 -0
  42. package/dist/data/compression/payload_compression_runner.d.ts.map +1 -0
  43. package/dist/data/compression/payload_compression_runner.js +55 -0
  44. package/dist/data/compression/payload_compression_runner.js.map +1 -0
  45. package/dist/data/compression/payload_compressor.d.ts +40 -0
  46. package/dist/data/compression/payload_compressor.d.ts.map +1 -0
  47. package/dist/data/compression/payload_compressor.js +126 -0
  48. package/dist/data/compression/payload_compressor.js.map +1 -0
  49. package/dist/data/compression/uri_compressible_asset.d.ts +23 -0
  50. package/dist/data/compression/uri_compressible_asset.d.ts.map +1 -0
  51. package/dist/data/compression/uri_compressible_asset.js +77 -0
  52. package/dist/data/compression/uri_compressible_asset.js.map +1 -0
  53. package/dist/data/dsl/builder/cache_config_builder.d.ts +2 -1
  54. package/dist/data/dsl/builder/cache_config_builder.d.ts.map +1 -1
  55. package/dist/data/dsl/builder/cache_config_builder.js +2 -1
  56. package/dist/data/dsl/builder/cache_config_builder.js.map +1 -1
  57. package/dist/data/dsl/builder/config_builder.d.ts +40 -14
  58. package/dist/data/dsl/builder/config_builder.d.ts.map +1 -1
  59. package/dist/data/dsl/builder/config_builder.js +40 -14
  60. package/dist/data/dsl/builder/config_builder.js.map +1 -1
  61. package/dist/data/dsl/builder/interceptor_config_builder.d.ts +9 -7
  62. package/dist/data/dsl/builder/interceptor_config_builder.d.ts.map +1 -1
  63. package/dist/data/dsl/builder/interceptor_config_builder.js +9 -7
  64. package/dist/data/dsl/builder/interceptor_config_builder.js.map +1 -1
  65. package/dist/data/dsl/builder/retry_config_builder.d.ts +16 -1
  66. package/dist/data/dsl/builder/retry_config_builder.d.ts.map +1 -1
  67. package/dist/data/dsl/builder/retry_config_builder.js +16 -1
  68. package/dist/data/dsl/builder/retry_config_builder.js.map +1 -1
  69. package/dist/data/dsl/builder/timeout_config_builder.d.ts +4 -0
  70. package/dist/data/dsl/builder/timeout_config_builder.d.ts.map +1 -1
  71. package/dist/data/dsl/builder/timeout_config_builder.js +4 -0
  72. package/dist/data/dsl/builder/timeout_config_builder.js.map +1 -1
  73. package/dist/data/dsl/use_smile_id_network_builder.d.ts +17 -0
  74. package/dist/data/dsl/use_smile_id_network_builder.d.ts.map +1 -1
  75. package/dist/data/dsl/use_smile_id_network_builder.js +17 -0
  76. package/dist/data/dsl/use_smile_id_network_builder.js.map +1 -1
  77. package/dist/data/factory/networking_factory.d.ts.map +1 -1
  78. package/dist/data/factory/networking_factory.js +11 -1
  79. package/dist/data/factory/networking_factory.js.map +1 -1
  80. package/dist/data/repository/use_smile_id_repository_impl.d.ts +6 -0
  81. package/dist/data/repository/use_smile_id_repository_impl.d.ts.map +1 -1
  82. package/dist/data/repository/use_smile_id_repository_impl.js +82 -8
  83. package/dist/data/repository/use_smile_id_repository_impl.js.map +1 -1
  84. package/dist/data/security/request_canonicalizer.d.ts.map +1 -1
  85. package/dist/data/security/request_canonicalizer.js +14 -2
  86. package/dist/data/security/request_canonicalizer.js.map +1 -1
  87. package/dist/data/security/security_policy.d.ts +43 -0
  88. package/dist/data/security/security_policy.d.ts.map +1 -0
  89. package/dist/data/security/security_policy.js +61 -0
  90. package/dist/data/security/security_policy.js.map +1 -0
  91. package/dist/data/util/retry_util.d.ts +1 -1
  92. package/dist/data/util/retry_util.d.ts.map +1 -1
  93. package/dist/data/util/retry_util.js +2 -1
  94. package/dist/data/util/retry_util.js.map +1 -1
  95. package/dist/data/util/use_smile_id_token_decoder.d.ts +22 -0
  96. package/dist/data/util/use_smile_id_token_decoder.d.ts.map +1 -1
  97. package/dist/data/util/use_smile_id_token_decoder.js +58 -24
  98. package/dist/data/util/use_smile_id_token_decoder.js.map +1 -1
  99. package/dist/l10n/strings_en.d.ts +2 -1
  100. package/dist/l10n/strings_en.d.ts.map +1 -1
  101. package/dist/l10n/strings_en.js +2 -1
  102. package/dist/l10n/strings_en.js.map +1 -1
  103. package/dist/l10n/use_smile_id_localizations.d.ts +1 -0
  104. package/dist/l10n/use_smile_id_localizations.d.ts.map +1 -1
  105. package/dist/l10n/use_smile_id_localizations.js +3 -0
  106. package/dist/l10n/use_smile_id_localizations.js.map +1 -1
  107. package/dist/metadata/builder_configuration.d.ts +39 -0
  108. package/dist/metadata/builder_configuration.d.ts.map +1 -0
  109. package/dist/metadata/builder_configuration.js +34 -0
  110. package/dist/metadata/builder_configuration.js.map +1 -0
  111. package/dist/metadata/captured_image_properties.d.ts +11 -0
  112. package/dist/metadata/captured_image_properties.d.ts.map +1 -0
  113. package/dist/metadata/captured_image_properties.js +11 -0
  114. package/dist/metadata/captured_image_properties.js.map +1 -0
  115. package/dist/metadata/device_orientation_collector.d.ts +17 -0
  116. package/dist/metadata/device_orientation_collector.d.ts.map +1 -0
  117. package/dist/metadata/device_orientation_collector.js +66 -0
  118. package/dist/metadata/device_orientation_collector.js.map +1 -0
  119. package/dist/metadata/geolocation.d.ts +20 -0
  120. package/dist/metadata/geolocation.d.ts.map +1 -0
  121. package/dist/metadata/geolocation.js +40 -0
  122. package/dist/metadata/geolocation.js.map +1 -0
  123. package/dist/metadata/metadata_configuration_mappers.d.ts +48 -0
  124. package/dist/metadata/metadata_configuration_mappers.d.ts.map +1 -0
  125. package/dist/metadata/metadata_configuration_mappers.js +171 -0
  126. package/dist/metadata/metadata_configuration_mappers.js.map +1 -0
  127. package/dist/metadata/model_configuration.d.ts +41 -0
  128. package/dist/metadata/model_configuration.d.ts.map +1 -0
  129. package/dist/metadata/model_configuration.js +44 -0
  130. package/dist/metadata/model_configuration.js.map +1 -0
  131. package/dist/metadata/optional_native_modules.d.ts +79 -3
  132. package/dist/metadata/optional_native_modules.d.ts.map +1 -1
  133. package/dist/metadata/optional_native_modules.js +108 -3
  134. package/dist/metadata/optional_native_modules.js.map +1 -1
  135. package/dist/metadata/use_smile_id_metadata.d.ts +45 -7
  136. package/dist/metadata/use_smile_id_metadata.d.ts.map +1 -1
  137. package/dist/metadata/use_smile_id_metadata.js +85 -13
  138. package/dist/metadata/use_smile_id_metadata.js.map +1 -1
  139. package/dist/metadata/use_smile_id_metadata_factory.d.ts +37 -0
  140. package/dist/metadata/use_smile_id_metadata_factory.d.ts.map +1 -1
  141. package/dist/metadata/use_smile_id_metadata_factory.js +184 -12
  142. package/dist/metadata/use_smile_id_metadata_factory.js.map +1 -1
  143. package/dist/presentation/assets/svg/document_shimmer_strings.d.ts +14 -0
  144. package/dist/presentation/assets/svg/document_shimmer_strings.d.ts.map +1 -0
  145. package/dist/presentation/assets/svg/document_shimmer_strings.js +14 -0
  146. package/dist/presentation/assets/svg/document_shimmer_strings.js.map +1 -0
  147. package/dist/presentation/assets/svg/instruction_svg_strings.d.ts +12 -4
  148. package/dist/presentation/assets/svg/instruction_svg_strings.d.ts.map +1 -1
  149. package/dist/presentation/assets/svg/instruction_svg_strings.js +19 -8
  150. package/dist/presentation/assets/svg/instruction_svg_strings.js.map +1 -1
  151. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.d.ts +0 -5
  152. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.d.ts.map +1 -1
  153. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.js +0 -5
  154. package/dist/presentation/components/helpers/enhanced_smart_selfie_animation_frames.js.map +1 -1
  155. package/dist/presentation/components/icons/index.d.ts +24 -8
  156. package/dist/presentation/components/icons/index.d.ts.map +1 -1
  157. package/dist/presentation/components/icons/index.js +31 -11
  158. package/dist/presentation/components/icons/index.js.map +1 -1
  159. package/dist/presentation/components/use_smile_id_attribution.d.ts +1 -1
  160. package/dist/presentation/components/use_smile_id_button.d.ts +2 -2
  161. package/dist/presentation/components/use_smile_id_button.js +4 -4
  162. package/dist/presentation/components/use_smile_id_button.js.map +1 -1
  163. package/dist/presentation/components/use_smile_id_capture_content.d.ts +13 -0
  164. package/dist/presentation/components/use_smile_id_capture_content.d.ts.map +1 -1
  165. package/dist/presentation/components/use_smile_id_capture_content.js +15 -9
  166. package/dist/presentation/components/use_smile_id_capture_content.js.map +1 -1
  167. package/dist/presentation/components/use_smile_id_captured_document_image.d.ts +2 -3
  168. package/dist/presentation/components/use_smile_id_captured_document_image.d.ts.map +1 -1
  169. package/dist/presentation/components/use_smile_id_captured_document_image.js +1 -3
  170. package/dist/presentation/components/use_smile_id_captured_document_image.js.map +1 -1
  171. package/dist/presentation/components/use_smile_id_document_capture_controls.d.ts +12 -6
  172. package/dist/presentation/components/use_smile_id_document_capture_controls.d.ts.map +1 -1
  173. package/dist/presentation/components/use_smile_id_document_capture_controls.js +59 -16
  174. package/dist/presentation/components/use_smile_id_document_capture_controls.js.map +1 -1
  175. package/dist/presentation/components/use_smile_id_document_overlay.d.ts +22 -5
  176. package/dist/presentation/components/use_smile_id_document_overlay.d.ts.map +1 -1
  177. package/dist/presentation/components/use_smile_id_document_overlay.js +80 -40
  178. package/dist/presentation/components/use_smile_id_document_overlay.js.map +1 -1
  179. package/dist/presentation/components/use_smile_id_document_shimmer.d.ts +15 -0
  180. package/dist/presentation/components/use_smile_id_document_shimmer.d.ts.map +1 -0
  181. package/dist/presentation/components/use_smile_id_document_shimmer.js +23 -0
  182. package/dist/presentation/components/use_smile_id_document_shimmer.js.map +1 -0
  183. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.d.ts.map +1 -1
  184. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js +1 -3
  185. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_clip.js.map +1 -1
  186. package/dist/presentation/components/use_smile_id_guideline_thumbnail.d.ts.map +1 -1
  187. package/dist/presentation/components/use_smile_id_guideline_thumbnail.js +84 -1
  188. package/dist/presentation/components/use_smile_id_guideline_thumbnail.js.map +1 -1
  189. package/dist/presentation/components/use_smile_id_instructions_hero.d.ts +3 -1
  190. package/dist/presentation/components/use_smile_id_instructions_hero.d.ts.map +1 -1
  191. package/dist/presentation/components/use_smile_id_instructions_hero.js +18 -3
  192. package/dist/presentation/components/use_smile_id_instructions_hero.js.map +1 -1
  193. package/dist/presentation/components/use_smile_id_oval_border_palette.d.ts.map +1 -1
  194. package/dist/presentation/components/use_smile_id_oval_border_palette.js +7 -3
  195. package/dist/presentation/components/use_smile_id_oval_border_palette.js.map +1 -1
  196. package/dist/presentation/flow/dsl/builder/config_builder.d.ts +12 -0
  197. package/dist/presentation/flow/dsl/builder/config_builder.d.ts.map +1 -1
  198. package/dist/presentation/flow/dsl/builder/config_builder.js +12 -0
  199. package/dist/presentation/flow/dsl/builder/config_builder.js.map +1 -1
  200. package/dist/presentation/flow/dsl/builder/ml_builder.d.ts +13 -4
  201. package/dist/presentation/flow/dsl/builder/ml_builder.d.ts.map +1 -1
  202. package/dist/presentation/flow/dsl/builder/ml_builder.js +3 -2
  203. package/dist/presentation/flow/dsl/builder/ml_builder.js.map +1 -1
  204. package/dist/presentation/flow/dsl/builder/screens_builder.d.ts +111 -10
  205. package/dist/presentation/flow/dsl/builder/screens_builder.d.ts.map +1 -1
  206. package/dist/presentation/flow/dsl/builder/screens_builder.js +111 -10
  207. package/dist/presentation/flow/dsl/builder/screens_builder.js.map +1 -1
  208. package/dist/presentation/flow/dsl/builder/theme_builder.d.ts +15 -2
  209. package/dist/presentation/flow/dsl/builder/theme_builder.d.ts.map +1 -1
  210. package/dist/presentation/flow/dsl/builder/theme_builder.js +15 -2
  211. package/dist/presentation/flow/dsl/builder/theme_builder.js.map +1 -1
  212. package/dist/presentation/flow/dsl/use_smile_id_builder.d.ts +6 -4
  213. package/dist/presentation/flow/dsl/use_smile_id_builder.d.ts.map +1 -1
  214. package/dist/presentation/flow/dsl/use_smile_id_builder.js.map +1 -1
  215. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts +62 -36
  216. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.d.ts.map +1 -1
  217. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js +62 -36
  218. package/dist/presentation/flow/dsl/use_smile_id_flow_builder.js.map +1 -1
  219. package/dist/presentation/flow/flow_navigation_manager.d.ts +39 -4
  220. package/dist/presentation/flow/flow_navigation_manager.d.ts.map +1 -1
  221. package/dist/presentation/flow/flow_navigation_manager.js +141 -10
  222. package/dist/presentation/flow/flow_navigation_manager.js.map +1 -1
  223. package/dist/presentation/flow/model/instruction_guideline.d.ts +9 -0
  224. package/dist/presentation/flow/model/instruction_guideline.d.ts.map +1 -1
  225. package/dist/presentation/flow/model/instructions_defaults.d.ts +11 -2
  226. package/dist/presentation/flow/model/instructions_defaults.d.ts.map +1 -1
  227. package/dist/presentation/flow/model/instructions_defaults.js +73 -25
  228. package/dist/presentation/flow/model/instructions_defaults.js.map +1 -1
  229. package/dist/presentation/flow/screen/orchestrated_capture_screen.d.ts.map +1 -1
  230. package/dist/presentation/flow/screen/orchestrated_capture_screen.js +36 -12
  231. package/dist/presentation/flow/screen/orchestrated_capture_screen.js.map +1 -1
  232. package/dist/presentation/flow/screen/orchestrated_instructions_screen.js +1 -1
  233. package/dist/presentation/flow/screen/orchestrated_instructions_screen.js.map +1 -1
  234. package/dist/presentation/flow/screen/orchestrated_preview_screen.d.ts.map +1 -1
  235. package/dist/presentation/flow/screen/orchestrated_preview_screen.js +3 -1
  236. package/dist/presentation/flow/screen/orchestrated_preview_screen.js.map +1 -1
  237. package/dist/presentation/flow/screen/orchestrated_processing_screen.js +3 -3
  238. package/dist/presentation/flow/screen/orchestrated_processing_screen.js.map +1 -1
  239. package/dist/presentation/flow/validation/job_type_validator.d.ts.map +1 -1
  240. package/dist/presentation/flow/validation/job_type_validator.js +14 -0
  241. package/dist/presentation/flow/validation/job_type_validator.js.map +1 -1
  242. package/dist/presentation/screen/helpers/capture_screen_helpers.d.ts.map +1 -1
  243. package/dist/presentation/screen/helpers/capture_screen_helpers.js +1 -0
  244. package/dist/presentation/screen/helpers/capture_screen_helpers.js.map +1 -1
  245. package/dist/presentation/screen/helpers/instructions_screen_helpers.d.ts +1 -0
  246. package/dist/presentation/screen/helpers/instructions_screen_helpers.d.ts.map +1 -1
  247. package/dist/presentation/screen/helpers/instructions_screen_helpers.js +6 -2
  248. package/dist/presentation/screen/helpers/instructions_screen_helpers.js.map +1 -1
  249. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js +3 -0
  250. package/dist/presentation/screen/helpers/scan_state_to_overlay_state.js.map +1 -1
  251. package/dist/presentation/screen/use_smile_id_capture_screen.d.ts +17 -1
  252. package/dist/presentation/screen/use_smile_id_capture_screen.d.ts.map +1 -1
  253. package/dist/presentation/screen/use_smile_id_capture_screen.js +153 -18
  254. package/dist/presentation/screen/use_smile_id_capture_screen.js.map +1 -1
  255. package/dist/presentation/screen/use_smile_id_document_preview_screen.d.ts +2 -3
  256. package/dist/presentation/screen/use_smile_id_document_preview_screen.d.ts.map +1 -1
  257. package/dist/presentation/screen/use_smile_id_document_preview_screen.js +16 -11
  258. package/dist/presentation/screen/use_smile_id_document_preview_screen.js.map +1 -1
  259. package/dist/presentation/screen/use_smile_id_instructions_screen.d.ts +2 -0
  260. package/dist/presentation/screen/use_smile_id_instructions_screen.d.ts.map +1 -1
  261. package/dist/presentation/screen/use_smile_id_instructions_screen.js +2 -1
  262. package/dist/presentation/screen/use_smile_id_instructions_screen.js.map +1 -1
  263. package/dist/presentation/screen/use_smile_id_preview_screen.d.ts +7 -6
  264. package/dist/presentation/screen/use_smile_id_preview_screen.d.ts.map +1 -1
  265. package/dist/presentation/screen/use_smile_id_preview_screen.js +9 -8
  266. package/dist/presentation/screen/use_smile_id_preview_screen.js.map +1 -1
  267. package/dist/presentation/screen/use_smile_id_processing_screen.d.ts +2 -3
  268. package/dist/presentation/screen/use_smile_id_processing_screen.d.ts.map +1 -1
  269. package/dist/presentation/screen/use_smile_id_processing_screen.js +3 -2
  270. package/dist/presentation/screen/use_smile_id_processing_screen.js.map +1 -1
  271. package/dist/presentation/theme/theme.d.ts +1 -1
  272. package/dist/presentation/theme/theme.d.ts.map +1 -1
  273. package/dist/presentation/theme/theme.js +20 -18
  274. package/dist/presentation/theme/theme.js.map +1 -1
  275. package/dist/presentation/utils/force_max_brightness.d.ts +4 -4
  276. package/package.json +26 -6
  277. package/src/__tests__/react_native_mock.tsx +8 -0
  278. package/src/camera/camera_defaults.ts +11 -9
  279. package/src/camera/orientation_degrees.ts +5 -6
  280. package/src/core/models/__tests__/document_type.test.ts +6 -4
  281. package/src/core/models/document_capture_mode.ts +27 -0
  282. package/src/core/models/document_type.ts +6 -4
  283. package/src/core/models/result.ts +9 -8
  284. package/src/data/api/interceptors/__tests__/smile_integrity_interceptor.test.ts +37 -1
  285. package/src/data/api/interceptors/__tests__/smile_request_mac_interceptor.test.ts +59 -1
  286. package/src/data/api/interceptors/smile_auth_interceptor.ts +1 -1
  287. package/src/data/api/interceptors/smile_integrity_interceptor.ts +20 -0
  288. package/src/data/api/interceptors/smile_request_mac_interceptor.ts +13 -0
  289. package/src/data/compression/__tests__/payload_compressor.test.ts +200 -0
  290. package/src/data/compression/__tests__/payload_compressor_real_jpeg.test.ts +222 -0
  291. package/src/data/compression/compressible_asset.ts +62 -0
  292. package/src/data/compression/payload_compression_config.ts +53 -0
  293. package/src/data/compression/payload_compression_runner.ts +102 -0
  294. package/src/data/compression/payload_compressor.ts +164 -0
  295. package/src/data/compression/uri_compressible_asset.ts +85 -0
  296. package/src/data/dsl/builder/cache_config_builder.ts +3 -1
  297. package/src/data/dsl/builder/config_builder.ts +45 -14
  298. package/src/data/dsl/builder/interceptor_config_builder.ts +9 -7
  299. package/src/data/dsl/builder/retry_config_builder.ts +22 -1
  300. package/src/data/dsl/builder/timeout_config_builder.ts +7 -0
  301. package/src/data/dsl/use_smile_id_network_builder.ts +17 -0
  302. package/src/data/factory/networking_factory.ts +14 -1
  303. package/src/data/repository/__tests__/network_retries.test.ts +117 -0
  304. package/src/data/repository/use_smile_id_repository_impl.ts +96 -9
  305. package/src/data/security/__tests__/security_policy.test.ts +96 -0
  306. package/src/data/security/__tests__/smile_id_crypto_manager.test.ts +18 -0
  307. package/src/data/security/request_canonicalizer.ts +15 -2
  308. package/src/data/security/security_policy.ts +70 -0
  309. package/src/data/util/__tests__/use_smile_id_token_decoder.test.ts +57 -1
  310. package/src/data/util/retry_util.ts +3 -2
  311. package/src/data/util/use_smile_id_token_decoder.ts +76 -28
  312. package/src/l10n/strings_en.ts +2 -1
  313. package/src/l10n/use_smile_id_localizations.ts +3 -0
  314. package/src/metadata/__tests__/device_orientation_collector.test.ts +15 -0
  315. package/src/metadata/__tests__/metadata_configuration_mappers.test.ts +215 -0
  316. package/src/metadata/__tests__/metadata_configuration_objects.test.ts +85 -0
  317. package/src/metadata/__tests__/metadata_device_context.test.ts +147 -0
  318. package/src/metadata/__tests__/metadata_golden_fixture.json +13 -0
  319. package/src/metadata/__tests__/metadata_payload_golden.test.ts +193 -0
  320. package/src/metadata/__tests__/use_smile_id_metadata.test.ts +18 -24
  321. package/src/metadata/builder_configuration.ts +63 -0
  322. package/src/metadata/captured_image_properties.ts +10 -0
  323. package/src/metadata/device_orientation_collector.ts +75 -0
  324. package/src/metadata/geolocation.ts +46 -0
  325. package/src/metadata/metadata_configuration_mappers.ts +234 -0
  326. package/src/metadata/model_configuration.ts +68 -0
  327. package/src/metadata/optional_native_modules.ts +157 -3
  328. package/src/metadata/use_smile_id_metadata.ts +140 -17
  329. package/src/metadata/use_smile_id_metadata_factory.ts +195 -11
  330. package/src/presentation/assets/si_ess_animation_all_directions.json +1300 -286
  331. package/src/presentation/assets/svg/document_shimmer_strings.ts +17 -0
  332. package/src/presentation/assets/svg/instruction_svg_strings.ts +34 -12
  333. package/src/presentation/components/__tests__/enhanced_smart_selfie_animation_frames.test.ts +1 -3
  334. package/src/presentation/components/__tests__/use_smile_id_enhanced_smart_selfie_clip.test.tsx +3 -4
  335. package/src/presentation/components/__tests__/use_smile_id_guideline_thumbnail.test.tsx +34 -1
  336. package/src/presentation/components/__tests__/use_smile_id_instructions_hero.test.tsx +27 -0
  337. package/src/presentation/components/__tests__/use_smile_id_oval_border_palette.test.ts +7 -5
  338. package/src/presentation/components/helpers/enhanced_smart_selfie_animation_frames.ts +0 -6
  339. package/src/presentation/components/icons/index.tsx +65 -18
  340. package/src/presentation/components/use_smile_id_attribution.tsx +1 -1
  341. package/src/presentation/components/use_smile_id_button.tsx +4 -4
  342. package/src/presentation/components/use_smile_id_capture_content.tsx +36 -12
  343. package/src/presentation/components/use_smile_id_captured_document_image.tsx +3 -8
  344. package/src/presentation/components/use_smile_id_document_capture_controls.tsx +103 -39
  345. package/src/presentation/components/use_smile_id_document_overlay.tsx +134 -44
  346. package/src/presentation/components/use_smile_id_document_shimmer.tsx +51 -0
  347. package/src/presentation/components/use_smile_id_enhanced_smart_selfie_clip.tsx +0 -3
  348. package/src/presentation/components/use_smile_id_guideline_thumbnail.tsx +131 -2
  349. package/src/presentation/components/use_smile_id_instructions_hero.tsx +100 -59
  350. package/src/presentation/components/use_smile_id_oval_border_palette.ts +7 -3
  351. package/src/presentation/flow/__tests__/document_flow_navigation.test.ts +200 -1
  352. package/src/presentation/flow/dsl/builder/config_builder.ts +14 -0
  353. package/src/presentation/flow/dsl/builder/ml_builder.ts +13 -4
  354. package/src/presentation/flow/dsl/builder/screens_builder.ts +111 -10
  355. package/src/presentation/flow/dsl/builder/theme_builder.ts +19 -2
  356. package/src/presentation/flow/dsl/use_smile_id_builder.tsx +6 -4
  357. package/src/presentation/flow/dsl/use_smile_id_flow_builder.ts +62 -36
  358. package/src/presentation/flow/flow_navigation_manager.ts +179 -13
  359. package/src/presentation/flow/model/instruction_guideline.ts +6 -0
  360. package/src/presentation/flow/model/instructions_defaults.ts +103 -25
  361. package/src/presentation/flow/screen/orchestrated_capture_screen.tsx +43 -10
  362. package/src/presentation/flow/screen/orchestrated_instructions_screen.tsx +1 -0
  363. package/src/presentation/flow/screen/orchestrated_preview_screen.tsx +4 -2
  364. package/src/presentation/flow/screen/orchestrated_processing_screen.tsx +3 -3
  365. package/src/presentation/flow/validation/__tests__/job_type_validator.test.ts +64 -0
  366. package/src/presentation/flow/validation/job_type_validator.ts +24 -0
  367. package/src/presentation/screen/__tests__/instructions_screen_helpers.test.ts +15 -3
  368. package/src/presentation/screen/__tests__/use_smile_id_capture_screen.test.tsx +6 -4
  369. package/src/presentation/screen/helpers/capture_screen_helpers.ts +1 -0
  370. package/src/presentation/screen/helpers/instructions_screen_helpers.ts +7 -2
  371. package/src/presentation/screen/helpers/scan_state_to_overlay_state.ts +3 -0
  372. package/src/presentation/screen/use_smile_id_capture_screen.tsx +234 -36
  373. package/src/presentation/screen/use_smile_id_document_preview_screen.tsx +32 -22
  374. package/src/presentation/screen/use_smile_id_instructions_screen.tsx +4 -0
  375. package/src/presentation/screen/use_smile_id_preview_screen.tsx +9 -9
  376. package/src/presentation/screen/use_smile_id_processing_screen.tsx +7 -7
  377. package/src/presentation/theme/theme.ts +21 -19
  378. package/src/presentation/utils/force_max_brightness.tsx +4 -4
  379. package/dist/camera/frame_processor_plugin.d.ts +0 -28
  380. package/dist/camera/frame_processor_plugin.d.ts.map +0 -1
  381. package/dist/camera/frame_processor_plugin.js +0 -21
  382. package/dist/camera/frame_processor_plugin.js.map +0 -1
  383. package/dist/camera/use_smile_id_frame_processor.d.ts +0 -38
  384. package/dist/camera/use_smile_id_frame_processor.d.ts.map +0 -1
  385. package/dist/camera/use_smile_id_frame_processor.js +0 -2
  386. package/dist/camera/use_smile_id_frame_processor.js.map +0 -1
  387. package/dist/presentation/components/use_smile_id_document_frame_glyph.d.ts +0 -17
  388. package/dist/presentation/components/use_smile_id_document_frame_glyph.d.ts.map +0 -1
  389. package/dist/presentation/components/use_smile_id_document_frame_glyph.js +0 -17
  390. package/dist/presentation/components/use_smile_id_document_frame_glyph.js.map +0 -1
  391. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_animation.d.ts +0 -24
  392. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_animation.d.ts.map +0 -1
  393. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_animation.js +0 -91
  394. package/dist/presentation/components/use_smile_id_enhanced_smart_selfie_animation.js.map +0 -1
  395. package/dist/presentation/components/use_smile_id_instruction_card.d.ts +0 -31
  396. package/dist/presentation/components/use_smile_id_instruction_card.d.ts.map +0 -1
  397. package/dist/presentation/components/use_smile_id_instruction_card.js +0 -43
  398. package/dist/presentation/components/use_smile_id_instruction_card.js.map +0 -1
  399. package/dist/presentation/screen/helpers/use_document_orientation_lock.d.ts +0 -24
  400. package/dist/presentation/screen/helpers/use_document_orientation_lock.d.ts.map +0 -1
  401. package/dist/presentation/screen/helpers/use_document_orientation_lock.js +0 -47
  402. package/dist/presentation/screen/helpers/use_document_orientation_lock.js.map +0 -1
  403. package/src/presentation/components/use_smile_id_document_frame_glyph.tsx +0 -162
@@ -99,6 +99,14 @@ export const I18nManager = { isRTL: false };
99
99
 
100
100
  export const useColorScheme = (): "light" | "dark" | null => "light";
101
101
 
102
+ // Portrait window by default — the capture screen reads this for its
103
+ // iOS preview-remount key (`cameraOrientationKey`); tests don't assert on
104
+ // the key, so a stable portrait size just lets the component render.
105
+ export const useWindowDimensions = (): { width: number; height: number } => ({
106
+ width: 390,
107
+ height: 844,
108
+ });
109
+
102
110
  export const Platform = {
103
111
  OS: "ios" as const,
104
112
  select<T>(spec: { ios?: T; android?: T; default?: T }): T | undefined {
@@ -1,23 +1,25 @@
1
1
  /** Static camera tuning defaults. */
2
2
  export const CameraDefaults = {
3
- /** Focus running indicator visibility duration in ms. */
3
+ /** Focus running indicator visibility duration (ms). */
4
4
  focusRunningDurationMs: 600,
5
- /** Focus settled indicator visibility duration in ms. */
5
+ /** Focus settled indicator visibility duration (ms). */
6
6
  focusSettledDurationMs: 500,
7
7
 
8
- /** Pinned analysis-stream frame rate. 30 fps keeps head motion smooth and per-frame exposure low-ISO. */
8
+ /** Pinned analysis-stream frame rate; 30 fps keeps motion smooth at low ISO. */
9
9
  analysisTargetFps: 30,
10
- /** Analysis-stream resolution (landscape). Matches the scan sessions' tuned input; higher stutters the frame-processor thread without improving detection. */
10
+ /** Selfie analysis resolution (4:3 — the face/smile heuristics are calibrated on it). */
11
11
  analysisTargetResolution: { width: 640, height: 480 } as const,
12
+ /** Document analysis resolution (16:9, matched to the visible preview so the guide-rect crop stays aligned). */
13
+ documentAnalysisResolution: { width: 1280, height: 720 } as const,
12
14
 
13
- /** Longest-edge ceiling (px) for an imported document image; downscale-only, never upscale. Matches the native auto-capture resample target so every upload shares one byte policy. */
15
+ /** Longest-edge ceiling (px) for an imported document image; downscale-only. */
14
16
  documentMaxDimension: 1600,
15
- /** JPEG compression quality (0.0–1.0, `expo-image-manipulator`'s `compress`) for imported document images; matches the auto-capture artifact quality. */
17
+ /** JPEG quality (0–1) for imported document images. */
16
18
  documentJpegQuality: 0.85,
17
- /** Minimum shortest-edge (px) for a gallery document image; smaller is rejected pre-resample since upscaling yields unreadable artifacts. */
19
+ /** Minimum shortest-edge (px) for a gallery document image; smaller is rejected. */
18
20
  minGalleryDocumentDimensionPx: 600,
19
- /** Default selfie JPEG width (px) when no `sdk_config` override is present; also the `image_width` metadata fallback. */
21
+ /** Default selfie JPEG width (px); also the `image_width` metadata fallback. */
20
22
  selfieJpegTargetWidth: 480,
21
- /** Default selfie JPEG height (px) when no `sdk_config` override is present; also the `image_height` metadata fallback. */
23
+ /** Default selfie JPEG height (px); also the `image_height` metadata fallback. */
22
24
  selfieJpegTargetHeight: 640,
23
25
  } as const;
@@ -1,12 +1,11 @@
1
1
  import type { Orientation } from "react-native-vision-camera";
2
2
 
3
3
  /**
4
- * Map VisionCamera's gravity-derived device {@link Orientation} to the
5
- * clockwise degrees the native scan sessions expect (`0` = upright
6
- * portrait). Must reflect the physical device tilt — feed it from
7
- * `onOutputOrientationChanged` with `outputOrientation="device"`. Never
8
- * derive it from `frame.orientation`: that reports the sensor mounting,
9
- * not how the user is holding the phone.
4
+ * Map a VisionCamera {@link Orientation} to clockwise degrees (`0` = portrait).
5
+ * Feeds two callers: `onOutputOrientationChanged` (gravity tilt → the upright
6
+ * validator) and `onPreviewOrientationChanged` (locked viewport axis → the
7
+ * document `viewportOrientation` the frame processor uprights the capture to).
8
+ * Never use `frame.orientation` — that is the fixed sensor mounting.
10
9
  */
11
10
  export function orientationToDegrees(orientation: Orientation): number {
12
11
  switch (orientation) {
@@ -5,21 +5,23 @@ import { DocumentType } from "../document_type";
5
5
  // / orientation drive both copy and capture behaviour). Pin them exactly so a
6
6
  // change forces a deliberate decision here and in the sibling SDKs.
7
7
  describe("DocumentType presets", () => {
8
- it("SouthAfricaGreenBook is a portrait, two-sided document", () => {
8
+ it("SouthAfricaGreenBook is a portrait, single-sided document", () => {
9
9
  expect(DocumentType.SouthAfricaGreenBook).toEqual({
10
10
  kind: "southAfricaGreenBook",
11
11
  displayName: "Green Book",
12
- hasBackSide: true,
12
+ hasBackSide: false,
13
13
  orientation: "Portrait",
14
+ knownAspectRatio: 320 / 428,
14
15
  });
15
16
  });
16
17
 
17
- it("Passport is a portrait, single-sided document", () => {
18
+ it("Passport is a landscape, single-sided document", () => {
18
19
  expect(DocumentType.Passport).toEqual({
19
20
  kind: "passport",
20
21
  displayName: "Passport",
21
22
  hasBackSide: false,
22
- orientation: "Portrait",
23
+ orientation: "Landscape",
24
+ knownAspectRatio: 356 / 272,
23
25
  });
24
26
  });
25
27
 
@@ -44,3 +44,30 @@ export const DocumentCaptureMode = {
44
44
  });
45
45
  },
46
46
  } as const;
47
+
48
+ /**
49
+ * The wire token the server expects for the `auto_capture_enabled` builder-configuration field,
50
+ * keyed off the mode's `kind`. Lives with {@link DocumentCaptureMode} — its single source of
51
+ * truth — so the metadata layer maps the partner's typed choice straight onto the wire without a
52
+ * parallel enum. These exact strings are the server contract; changing them is a wire-breaking
53
+ * change. Not part of the public package surface (not re-exported from `index.ts`).
54
+ */
55
+ export function autoCaptureWireValue(
56
+ mode: DocumentCaptureMode | undefined,
57
+ ): string {
58
+ switch (mode?.kind) {
59
+ case "autoCapture":
60
+ return "auto_capture_only";
61
+ case "manualCapture":
62
+ return "manual";
63
+ case "autoCaptureWithManualFallback":
64
+ case undefined:
65
+ return "autocapture_default";
66
+ default: {
67
+ // Exhaustiveness guard: a new DocumentCaptureMode kind fails to compile here
68
+ // until it's handled above (`mode` narrows to `never` once all kinds are covered).
69
+ const exhaustive: never = mode;
70
+ return exhaustive;
71
+ }
72
+ }
73
+ }
@@ -26,15 +26,17 @@ export type DocumentType =
26
26
  const SOUTH_AFRICA_GREEN_BOOK: DocumentType = Object.freeze({
27
27
  kind: "southAfricaGreenBook",
28
28
  displayName: "Green Book",
29
- hasBackSide: true,
29
+ hasBackSide: false,
30
30
  orientation: "Portrait",
31
+ knownAspectRatio: 320 / 428,
31
32
  });
32
33
 
33
34
  const PASSPORT: DocumentType = Object.freeze({
34
35
  kind: "passport",
35
36
  displayName: "Passport",
36
37
  hasBackSide: false,
37
- orientation: "Portrait",
38
+ orientation: "Landscape",
39
+ knownAspectRatio: 356 / 272,
38
40
  });
39
41
 
40
42
  /** Configurable fields for {@link DocumentType.GenericDocument}. */
@@ -47,9 +49,9 @@ export type GenericDocumentArgs = {
47
49
 
48
50
  /** Preset values and the `GenericDocument` factory for {@link DocumentType}. */
49
51
  export const DocumentType = {
50
- /** South Africa Green Book — booklet-style, two-sided, portrait. */
52
+ /** South Africa Green Book — booklet-style, single-side, portrait. */
51
53
  SouthAfricaGreenBook: SOUTH_AFRICA_GREEN_BOOK,
52
- /** Passport — single data page, no back side, portrait (provisional values). */
54
+ /** Passport — single data page, no back side, landscape. */
53
55
  Passport: PASSPORT,
54
56
  /** Catch-all without a preset; defaults to a landscape, two-sided "Document". */
55
57
  GenericDocument(args: GenericDocumentArgs = {}): DocumentType {
@@ -1,31 +1,32 @@
1
1
  /**
2
- * A result type for SDK operations that makes error handling explicit.
2
+ * The outcome of an SDK operation: success with a value, failure with an
3
+ * error, or cancellation when the user exits the flow. Surfaced to
4
+ * partners through `onResult`.
3
5
  *
4
6
  * Partners never need to unwrap a `UseSmileIDResult` directly — all
5
7
  * errors are automatically surfaced through the `onResult` callback.
6
8
  * The discriminated union lets partner code `switch (result.status)`
7
- * and exhaust both branches without try/catch ceremony.
9
+ * and exhaust the branches without try/catch ceremony.
8
10
  */
9
11
  export type UseSmileIDResult<T> =
10
12
  | { readonly status: "success"; readonly value: T }
11
13
  | { readonly status: "failure"; readonly error: unknown }
12
14
  | { readonly status: "cancelled" };
13
15
 
14
- /** Construct a successful result carrying `value`. */
16
+ /** A successful outcome carrying the resulting value. */
15
17
  export function success<T>(value: T): UseSmileIDResult<T> {
16
18
  return { status: "success", value };
17
19
  }
18
20
 
19
- /** Construct a failed result carrying `error`. */
21
+ /** A failed outcome carrying the causing error. */
20
22
  export function failure<T>(error: unknown): UseSmileIDResult<T> {
21
23
  return { status: "failure", error };
22
24
  }
23
25
 
24
26
  /**
25
- * Construct a cancelled result — the user deliberately exited the flow
26
- * (e.g. back-pressed out of the first screen) without completing or
27
- * failing. Distinct from `failure` so partners can tell a user
28
- * cancellation apart from an error.
27
+ * The user deliberately exited the flow without completing or failing it
28
+ * (e.g. back-pressed out of the first screen) — distinct from a failure
29
+ * so cancellation can be handled separately.
29
30
  */
30
31
  export function cancelled<T>(): UseSmileIDResult<T> {
31
32
  return { status: "cancelled" };
@@ -20,6 +20,7 @@ import {
20
20
  ATTESTATION_OPT_IN_KEY,
21
21
  } from "../smile_integrity_interceptor";
22
22
  import { REQUEST_MAC_HEADER } from "../smile_request_mac_interceptor";
23
+ import { SecurityPolicy } from "../../../security/security_policy";
23
24
 
24
25
  /**
25
26
  * Pins down the integrity interceptor's wire shape:
@@ -51,7 +52,10 @@ describe("SmileIntegrityInterceptor", () => {
51
52
 
52
53
  beforeEach(() => {
53
54
  client = axios.create();
54
- interceptor = new SmileIntegrityInterceptor();
55
+ // undefined bit-code is fail-secure: the attestation check is active, matching the pre-policy default.
56
+ interceptor = new SmileIntegrityInterceptor({
57
+ securityPolicy: SecurityPolicy.fromBitCode(undefined),
58
+ });
55
59
  interceptor.attach(client);
56
60
  warmUpAttestation = vi.fn();
57
61
  requestAttestationToken = vi.fn();
@@ -153,4 +157,36 @@ describe("SmileIntegrityInterceptor", () => {
153
157
  expect(ex.message).toBe("hello world");
154
158
  expect(encodeURIComponent(ex.message)).toBe("hello%20world");
155
159
  });
160
+
161
+ it("passes through without a nonce when policy disables attestation", async () => {
162
+ // bit-code 1: only payload signing (bit 0) set; the attestation bit (3) is off, so the token
163
+ // must not be requested even though the MAC header is present.
164
+ const localClient = axios.create();
165
+ new SmileIntegrityInterceptor({
166
+ securityPolicy: SecurityPolicy.fromBitCode(1),
167
+ }).attach(localClient);
168
+ const config = makeConfig({
169
+ [ATTESTATION_OPT_IN_KEY]: true,
170
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
171
+ } as any);
172
+ config.headers.set(REQUEST_MAC_HEADER, "mac-abc");
173
+
174
+ const handlers = (
175
+ localClient.interceptors.request as unknown as {
176
+ handlers: {
177
+ fulfilled?: (cfg: InternalAxiosRequestConfig) => unknown;
178
+ }[];
179
+ }
180
+ ).handlers;
181
+ let current = config;
182
+ for (let i = handlers.length - 1; i >= 0; i--) {
183
+ const handler = handlers[i]?.fulfilled;
184
+ if (typeof handler === "function") {
185
+ current = (await handler(current)) as InternalAxiosRequestConfig;
186
+ }
187
+ }
188
+
189
+ expect(current.headers.get(DEVICE_NONCE_HEADER)).toBeUndefined();
190
+ expect(requestAttestationToken).not.toHaveBeenCalled();
191
+ });
156
192
  });
@@ -2,6 +2,7 @@ import { describe, expect, it, beforeEach } from "vitest";
2
2
  import axios, { AxiosHeaders, type InternalAxiosRequestConfig } from "axios";
3
3
 
4
4
  import { SmileIDCryptoManager } from "../../../security/smile_id_crypto_manager";
5
+ import { SecurityPolicy } from "../../../security/security_policy";
5
6
  import {
6
7
  SmileRequestMacInterceptor,
7
8
  REQUEST_MAC_HEADER,
@@ -26,7 +27,11 @@ describe("SmileRequestMacInterceptor", () => {
26
27
 
27
28
  beforeEach(() => {
28
29
  client = axios.create();
29
- interceptor = new SmileRequestMacInterceptor({ cryptoManager });
30
+ // undefined bit-code is fail-secure: payload signing is active, matching the pre-policy default.
31
+ interceptor = new SmileRequestMacInterceptor({
32
+ cryptoManager,
33
+ securityPolicy: SecurityPolicy.fromBitCode(undefined),
34
+ });
30
35
  interceptor.attach(client);
31
36
  });
32
37
 
@@ -95,4 +100,57 @@ describe("SmileRequestMacInterceptor", () => {
95
100
  );
96
101
  expect(result.headers.get(REQUEST_MAC_HEADER)).toBeUndefined();
97
102
  });
103
+
104
+ /** Drive a one-interceptor client built with a specific policy. */
105
+ async function runWith(
106
+ policy: SecurityPolicy,
107
+ config: InternalAxiosRequestConfig,
108
+ ): Promise<InternalAxiosRequestConfig> {
109
+ const localClient = axios.create();
110
+ new SmileRequestMacInterceptor({
111
+ cryptoManager,
112
+ securityPolicy: policy,
113
+ }).attach(localClient);
114
+ const handlers = (
115
+ localClient.interceptors.request as unknown as {
116
+ handlers: {
117
+ fulfilled?: (cfg: InternalAxiosRequestConfig) => unknown;
118
+ }[];
119
+ }
120
+ ).handlers;
121
+ let current = config;
122
+ for (let i = handlers.length - 1; i >= 0; i--) {
123
+ const handler = handlers[i]?.fulfilled;
124
+ if (typeof handler === "function") {
125
+ current = (await handler(current)) as InternalAxiosRequestConfig;
126
+ }
127
+ }
128
+ return current;
129
+ }
130
+
131
+ it("leaves the request unsigned when the policy disables payload signing", async () => {
132
+ // bit-code 0: no bits set, so payload signing is off for this session.
133
+ const result = await runWith(
134
+ SecurityPolicy.fromBitCode(0),
135
+ makeConfig({
136
+ data: '{"hello":"world"}',
137
+ headers: new AxiosHeaders({ [REQUEST_TIMESTAMP_HEADER]: TIMESTAMP }),
138
+ [REQUEST_MAC_OPT_IN_KEY]: true,
139
+ } as Partial<InternalAxiosRequestConfig>),
140
+ );
141
+ expect(result.headers.get(REQUEST_MAC_HEADER)).toBeUndefined();
142
+ });
143
+
144
+ it("signs when the policy enables only payload signing", async () => {
145
+ // bit-code 1: bit 0 (payload signing) set; other behaviours off.
146
+ const result = await runWith(
147
+ SecurityPolicy.fromBitCode(1),
148
+ makeConfig({
149
+ data: '{"hello":"world"}',
150
+ headers: new AxiosHeaders({ [REQUEST_TIMESTAMP_HEADER]: TIMESTAMP }),
151
+ [REQUEST_MAC_OPT_IN_KEY]: true,
152
+ } as Partial<InternalAxiosRequestConfig>),
153
+ );
154
+ expect(result.headers.get(REQUEST_MAC_HEADER)).toBeDefined();
155
+ });
98
156
  });
@@ -26,7 +26,7 @@ import type { TokenHolder } from "../auth/token_holder";
26
26
  * by the next outgoing request.
27
27
  */
28
28
 
29
- const SDK_VERSION = "12.0.0-beta01";
29
+ const SDK_VERSION = "12.0.0";
30
30
 
31
31
  /**
32
32
  * Source-SDK identifier, suffixed with the runtime platform so the server can
@@ -2,6 +2,7 @@ import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";
2
2
 
3
3
  import { Attestation, AttestationException } from "@smileid/usesmileid_bridge";
4
4
 
5
+ import type { SecurityPolicy } from "../../security/security_policy";
5
6
  import { REQUEST_MAC_HEADER } from "./smile_request_mac_interceptor";
6
7
 
7
8
  /**
@@ -31,6 +32,11 @@ import { REQUEST_MAC_HEADER } from "./smile_request_mac_interceptor";
31
32
  * Warm-up lives outside the interceptor. Call `Attestation.warmUp()`
32
33
  * once at flow mount so the per-request `requestToken` call doesn't
33
34
  * pay the multi-second warm-up cost on every submission.
35
+ *
36
+ * Attestation is additionally gated by the server-driven {@link SecurityPolicy}:
37
+ * when the session's policy disables the device-attestation check, an opted-in
38
+ * request ships without a device nonce. The policy is fail-secure, so a token
39
+ * that omits the claim attests as before.
34
40
  */
35
41
 
36
42
  export const DEVICE_NONCE_HEADER = "smileid-device-nonce";
@@ -38,7 +44,17 @@ export const DEVICE_NONCE_HEADER = "smileid-device-nonce";
38
44
  /** Axios config key that opts a request into attestation stamping. */
39
45
  export const ATTESTATION_OPT_IN_KEY = "_useSmileIDAttestation";
40
46
 
47
+ export type SmileIntegrityInterceptorOptions = {
48
+ readonly securityPolicy: SecurityPolicy;
49
+ };
50
+
41
51
  export class SmileIntegrityInterceptor {
52
+ private readonly _securityPolicy: SecurityPolicy;
53
+
54
+ constructor(options: SmileIntegrityInterceptorOptions) {
55
+ this._securityPolicy = options.securityPolicy;
56
+ }
57
+
42
58
  attach(client: AxiosInstance): { request: number } {
43
59
  const requestId = client.interceptors.request.use(async (config) => {
44
60
  return this._onRequest(config);
@@ -55,6 +71,10 @@ export class SmileIntegrityInterceptor {
55
71
  ] === true;
56
72
  if (!optedIn) return config;
57
73
 
74
+ // Server-driven policy can switch device attestation off for this session; when it is off,
75
+ // ship the opted-in request without requesting a token.
76
+ if (!this._securityPolicy.isPlayIntegrityCheckActive()) return config;
77
+
58
78
  const macHeader = config.headers.get(REQUEST_MAC_HEADER);
59
79
  if (typeof macHeader !== "string") return config;
60
80
 
@@ -2,6 +2,7 @@ import type { AxiosInstance, InternalAxiosRequestConfig } from "axios";
2
2
 
3
3
  import { SmileIDCryptoError } from "../../security/smile_id_crypto_error";
4
4
  import type { SmileIDCryptoManager } from "../../security/smile_id_crypto_manager";
5
+ import type { SecurityPolicy } from "../../security/security_policy";
5
6
 
6
7
  /**
7
8
  * Stamps the `smileid-request-mac` header on every outbound request whose axios
@@ -18,6 +19,11 @@ import type { SmileIDCryptoManager } from "../../security/smile_id_crypto_manage
18
19
  * rather than signed against an invented timestamp.
19
20
  *
20
21
  * Requests without the opt-in flag pass through unchanged.
22
+ *
23
+ * Signing is additionally gated by the server-driven {@link SecurityPolicy}: when the session's
24
+ * policy disables payload signing, an opted-in request still ships, just unsigned — the same wire
25
+ * shape as an opt-out request. The policy is fail-secure, so a token that omits the claim signs as
26
+ * before.
21
27
  */
22
28
 
23
29
  export const REQUEST_MAC_HEADER = "smileid-request-mac";
@@ -28,13 +34,16 @@ export const REQUEST_MAC_OPT_IN_KEY = "_useSmileIDRequestMac";
28
34
 
29
35
  export type SmileRequestMacInterceptorOptions = {
30
36
  readonly cryptoManager: SmileIDCryptoManager;
37
+ readonly securityPolicy: SecurityPolicy;
31
38
  };
32
39
 
33
40
  export class SmileRequestMacInterceptor {
34
41
  private readonly _cryptoManager: SmileIDCryptoManager;
42
+ private readonly _securityPolicy: SecurityPolicy;
35
43
 
36
44
  constructor(options: SmileRequestMacInterceptorOptions) {
37
45
  this._cryptoManager = options.cryptoManager;
46
+ this._securityPolicy = options.securityPolicy;
38
47
  }
39
48
 
40
49
  attach(client: AxiosInstance): { request: number } {
@@ -53,6 +62,10 @@ export class SmileRequestMacInterceptor {
53
62
  ] === true;
54
63
  if (!optedIn) return config;
55
64
 
65
+ // Server-driven policy can switch payload signing off for this session; when it is off, ship
66
+ // the opted-in request unsigned and let the server apply its policy.
67
+ if (!this._securityPolicy.isPayloadSigningActive()) return config;
68
+
56
69
  const timestamp = config.headers.get(REQUEST_TIMESTAMP_HEADER);
57
70
  if (typeof timestamp !== "string") return config;
58
71
 
@@ -0,0 +1,200 @@
1
+ import { ImageType, type ImageTypeId } from "@smileid/usesmileid_bridge";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ type CompressibleAsset,
6
+ CompressibleAssets,
7
+ } from "../compressible_asset";
8
+ import {
9
+ base64Len,
10
+ PayloadCompressionConfig as C,
11
+ } from "../payload_compression_config";
12
+ import { PayloadCompressor } from "../payload_compressor";
13
+
14
+ const mib = C.bytesPerMB;
15
+
16
+ /** Encoded size scales linearly with quality: `size(q) = base * q`. */
17
+ class FakeAsset implements CompressibleAsset {
18
+ recompressCount = 0;
19
+ private q: number;
20
+ constructor(
21
+ readonly imageType: ImageTypeId,
22
+ private readonly base: number,
23
+ startQuality: number,
24
+ ) {
25
+ this.q = startQuality;
26
+ }
27
+ encodedSize(): number {
28
+ return Math.trunc(this.base * this.q);
29
+ }
30
+ quality(): number {
31
+ return this.q;
32
+ }
33
+ async recompress(quality: number): Promise<void> {
34
+ this.q = quality;
35
+ this.recompressCount += 1;
36
+ }
37
+ }
38
+
39
+ describe("PayloadCompressor", () => {
40
+ it("base64Len matches ceil(n / 3) * 4", () => {
41
+ expect(base64Len(0)).toBe(0);
42
+ expect(base64Len(1)).toBe(4);
43
+ expect(base64Len(3)).toBe(4);
44
+ expect(base64Len(4)).toBe(8);
45
+ expect(base64Len(6)).toBe(8);
46
+ expect(base64Len(3_000_000)).toBe(Math.ceil(3_000_000 / 3) * 4);
47
+ });
48
+
49
+ it("raw and projected sizes sum images plus metadata", () => {
50
+ const assets = new CompressibleAssets({
51
+ selfie: new FakeAsset(ImageType.Selfie, 300_000, 1.0),
52
+ liveness: [new FakeAsset(ImageType.Liveness, 100_000, 1.0)],
53
+ });
54
+ const metadata = 50_000;
55
+ expect(PayloadCompressor.rawBundleSize(assets, metadata)).toBe(
56
+ 300_000 + 100_000 + 50_000,
57
+ );
58
+ expect(PayloadCompressor.projectedEncodedSize(assets, metadata)).toBe(
59
+ base64Len(300_000) +
60
+ base64Len(100_000) +
61
+ metadata +
62
+ C.structuralOverheadBytes,
63
+ );
64
+ });
65
+
66
+ it("plan order Rule B - selfie over 1MB compresses faces first then back then front", () => {
67
+ const assets = new CompressibleAssets({
68
+ selfie: new FakeAsset(ImageType.Selfie, 2 * mib, 0.95),
69
+ liveness: [new FakeAsset(ImageType.Liveness, mib, 0.8)],
70
+ documentFront: new FakeAsset(ImageType.DocumentFront, mib, 0.85),
71
+ documentBack: new FakeAsset(ImageType.DocumentBack, mib, 0.85),
72
+ });
73
+ const plan = PayloadCompressor.buildCompressionPlan(assets);
74
+ expect(plan.map(([a]) => a.imageType)).toEqual([
75
+ ImageType.Selfie,
76
+ ImageType.Liveness,
77
+ ImageType.DocumentBack,
78
+ ImageType.DocumentFront,
79
+ ]);
80
+ expect(plan[0][1]).toBe(C.faceMinQ);
81
+ expect(plan[3][1]).toBe(C.docMinQ);
82
+ });
83
+
84
+ it("plan order Rule A - documents over 4MB compresses back, front, then faces", () => {
85
+ const assets = new CompressibleAssets({
86
+ selfie: new FakeAsset(ImageType.Selfie, 500_000, 0.95),
87
+ documentFront: new FakeAsset(ImageType.DocumentFront, 3 * mib, 0.9),
88
+ documentBack: new FakeAsset(ImageType.DocumentBack, 3 * mib, 0.9),
89
+ });
90
+ const plan = PayloadCompressor.buildCompressionPlan(assets);
91
+ expect(plan.map(([a]) => a.imageType)).toEqual([
92
+ ImageType.DocumentBack,
93
+ ImageType.DocumentFront,
94
+ ImageType.Selfie,
95
+ ]);
96
+ });
97
+
98
+ it("plan order generic - small selfie and docs uses faces then docs", () => {
99
+ const assets = new CompressibleAssets({
100
+ selfie: new FakeAsset(ImageType.Selfie, 400_000, 0.95),
101
+ documentFront: new FakeAsset(ImageType.DocumentFront, 400_000, 0.85),
102
+ });
103
+ const plan = PayloadCompressor.buildCompressionPlan(assets);
104
+ expect(plan.map(([a]) => a.imageType)).toEqual([
105
+ ImageType.Selfie,
106
+ ImageType.DocumentFront,
107
+ ]);
108
+ });
109
+
110
+ it("already small bundle is left untouched", async () => {
111
+ const selfie = new FakeAsset(ImageType.Selfie, mib, 0.95);
112
+ await PayloadCompressor.prepareBundleForTransmission(
113
+ new CompressibleAssets({ selfie }),
114
+ 50_000,
115
+ );
116
+ expect(selfie.recompressCount).toBe(0);
117
+ });
118
+
119
+ it("over-budget bundle compresses under target and never breaches floors", async () => {
120
+ const selfie = new FakeAsset(
121
+ ImageType.Selfie,
122
+ Math.trunc((22 * mib) / 10),
123
+ 0.95,
124
+ );
125
+ const liveness = [
126
+ new FakeAsset(ImageType.Liveness, Math.trunc((12 * mib) / 10), 0.8),
127
+ new FakeAsset(ImageType.Liveness, Math.trunc((12 * mib) / 10), 0.8),
128
+ ];
129
+ const assets = new CompressibleAssets({ selfie, liveness });
130
+ const metadata = Math.trunc((2 * mib) / 10);
131
+ await PayloadCompressor.prepareBundleForTransmission(assets, metadata);
132
+
133
+ expect(
134
+ PayloadCompressor.rawBundleSize(assets, metadata),
135
+ ).toBeLessThanOrEqual(C.rawFallback);
136
+ expect(selfie.quality()).toBeGreaterThanOrEqual(C.faceMinQ);
137
+ for (const frame of liveness)
138
+ expect(frame.quality()).toBeGreaterThanOrEqual(C.faceMinQ);
139
+ });
140
+
141
+ it("document-heavy bundle compresses back first and never breaches the doc floor", async () => {
142
+ const front = new FakeAsset(
143
+ ImageType.DocumentFront,
144
+ Math.trunc((23 * mib) / 10),
145
+ 0.9,
146
+ );
147
+ const back = new FakeAsset(
148
+ ImageType.DocumentBack,
149
+ Math.trunc((23 * mib) / 10),
150
+ 0.9,
151
+ );
152
+ const selfie = new FakeAsset(ImageType.Selfie, 400_000, 0.95);
153
+ const assets = new CompressibleAssets({
154
+ selfie,
155
+ documentFront: front,
156
+ documentBack: back,
157
+ });
158
+ await PayloadCompressor.prepareBundleForTransmission(assets, 100_000);
159
+ expect(back.quality()).toBeLessThanOrEqual(front.quality());
160
+ expect(front.quality()).toBeGreaterThanOrEqual(C.docMinQ);
161
+ expect(back.quality()).toBeGreaterThanOrEqual(C.docMinQ);
162
+ expect(
163
+ PayloadCompressor.rawBundleSize(assets, 100_000),
164
+ ).toBeLessThanOrEqual(C.rawTarget);
165
+ });
166
+
167
+ it("hard guarantee drops past the per-type floor to fit the relaxed cap", async () => {
168
+ // floor .45 -> 12MB*.45 = 5.4MB raw, still over the relaxed 4.5MB ceiling. The guarantee pass
169
+ // must step quality below faceMinQ until the bundle fits both caps.
170
+ const selfie = new FakeAsset(ImageType.Selfie, 12 * mib, 0.95);
171
+ const assets = new CompressibleAssets({ selfie });
172
+ await PayloadCompressor.prepareBundleForTransmission(assets, 100_000);
173
+ expect(
174
+ PayloadCompressor.rawBundleSize(assets, 100_000),
175
+ ).toBeLessThanOrEqual(C.rawFallback);
176
+ expect(
177
+ PayloadCompressor.projectedEncodedSize(assets, 100_000),
178
+ ).toBeLessThanOrEqual(C.hardEncodedCap);
179
+ expect(selfie.quality()).toBeLessThan(C.faceMinQ);
180
+ expect(selfie.quality()).toBeGreaterThanOrEqual(C.absoluteMinQ);
181
+ });
182
+
183
+ it("extreme bundle unreachable by quality drives to the absolute floor without throwing", async () => {
184
+ // So large that even absoluteMinQ leaves it over the cap (real JPEGs shrink far more than this
185
+ // linear fake). The guarantee pass drives to the absolute floor and ships — never loops forever.
186
+ const selfie = new FakeAsset(ImageType.Selfie, 80 * mib, 0.95);
187
+ const assets = new CompressibleAssets({ selfie });
188
+ await PayloadCompressor.prepareBundleForTransmission(assets, 100_000);
189
+ expect(selfie.quality()).toBe(C.absoluteMinQ);
190
+ });
191
+
192
+ it("bundle in fallback band with light metadata is accepted", async () => {
193
+ const selfie = new FakeAsset(ImageType.Selfie, 9 * mib, 0.95);
194
+ const assets = new CompressibleAssets({ selfie });
195
+ await PayloadCompressor.prepareBundleForTransmission(assets, 100_000);
196
+ const raw = PayloadCompressor.rawBundleSize(assets, 100_000);
197
+ expect(raw).toBeGreaterThan(C.rawTarget);
198
+ expect(raw).toBeLessThanOrEqual(C.rawFallback);
199
+ });
200
+ });