@incodetech/core 2.3.2 → 2.4.0-rc.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 (267) hide show
  1. package/THIRD_PARTY_NOTICES.txt +1243 -3
  2. package/dist/Actor-DpTj5PFb.d.ts +1 -0
  3. package/dist/{BaseWasmProvider-Dyi6dKp1.d.ts → BaseWasmProvider-BL9wIRnN.d.ts} +2 -2
  4. package/dist/{FaceDetectionProvider-DtmtCKa9.esm.js → FaceDetectionProvider-BjTer9h3.esm.js} +126 -10
  5. package/dist/HttpRequestError-Bh0pNGSC.esm.js +35 -0
  6. package/dist/{IIdCaptureCapability-RNiFhvXR.d.ts → IIdCaptureCapability-DBsv9-7M.d.ts} +2 -2
  7. package/dist/{IWasmUtilCapability-CNWRl7jr.d.ts → IWasmUtilCapability-CPX0RGdQ.d.ts} +28 -5
  8. package/dist/{Manager-CQQ99QOI.d.ts → Manager-C9T5amUN.d.ts} +7 -1
  9. package/dist/{MotionSensorProvider-DyhbM4Si.esm.js → MotionSensorProvider-DYxh8TjR.esm.js} +1 -1
  10. package/dist/{ekycStateMachine-UeJRO8_p.esm.js → PhoneValidationProvider-SZiOS0jB.esm.js} +232 -827
  11. package/dist/{StreamCanvasCapture-_8gdW5Lk.d.ts → StreamCanvasCapture-DVmzJux5.d.ts} +2 -2
  12. package/dist/{safeStorage-BdU9_m2E.esm.js → WasmUtilProvider-CIdnE7Fx.esm.js} +95 -46
  13. package/dist/{addressSearch-DyHGgeX8.esm.js → addressSearch-BiYZjvp3.esm.js} +143 -4
  14. package/dist/{advanceableErrors-CpF2cBy6.esm.js → advanceableErrors-q_pfU3Cn.esm.js} +5 -4
  15. package/dist/ae-signature.d.ts +2 -2
  16. package/dist/ae-signature.esm.js +1 -1
  17. package/dist/antifraud.d.ts +1 -1
  18. package/dist/antifraud.esm.js +2 -2
  19. package/dist/{antifraudManager-BY36bS12.d.ts → antifraudManager-DbGCvlJs.d.ts} +2 -2
  20. package/dist/{antifraudManager-DNkw66oW.esm.js → antifraudManager-HL08jeDX.esm.js} +3 -3
  21. package/dist/{antifraudStateMachine-DBeq47D2.esm.js → antifraudStateMachine-DoJVzblM.esm.js} +3 -3
  22. package/dist/{apiError-CmGEmaGU.esm.js → apiError-DcSWedC6.esm.js} +2 -1
  23. package/dist/authentication.d.ts +3 -3
  24. package/dist/authentication.esm.js +2 -2
  25. package/dist/{authenticationManager-CboRCl_N.d.ts → authenticationManager-CANv5-W5.d.ts} +2 -8
  26. package/dist/{authenticationManager-BFjSjtQ_.esm.js → authenticationManager-SaiAxqvS.esm.js} +6 -6
  27. package/dist/{authenticationStateMachine-Dfo34ZiI.esm.js → authenticationStateMachine-CHSxmV7z.esm.js} +5 -6
  28. package/dist/avatar.d.ts +1 -1
  29. package/dist/{backCameraStream-B0-YgP_y.esm.js → backCameraStream-CMvXj4X4.esm.js} +1 -1
  30. package/dist/camera.esm.js +1 -1
  31. package/dist/certificate-issuance.d.ts +1 -1
  32. package/dist/certificate-issuance.esm.js +2 -2
  33. package/dist/{certificateIssuanceManager-C3-CI0_s.esm.js → certificateIssuanceManager-CMg4SgL4.esm.js} +3 -3
  34. package/dist/{certificateIssuanceManager-DCgEidHz.d.ts → certificateIssuanceManager-Dr8A4Lgr.d.ts} +2 -3
  35. package/dist/{certificateIssuanceStateMachine-CxqS_nD-.esm.js → certificateIssuanceStateMachine-Cs64QmYH.esm.js} +4 -4
  36. package/dist/consent.d.ts +1 -1
  37. package/dist/consent.esm.js +2 -2
  38. package/dist/{consentManager-B6E1XJDw.esm.js → consentManager-CjrxY737.esm.js} +3 -3
  39. package/dist/{consentManager-2h_w6wFc.d.ts → consentManager-DgzWQpAR.d.ts} +6 -7
  40. package/dist/{consentStateMachine-DTSfdGiW.esm.js → consentStateMachine-BCj5Jb3g.esm.js} +3 -3
  41. package/dist/{createCameraCycleOwnership-DnwTl1ad.esm.js → createCameraCycleOwnership-kdlhkmmY.esm.js} +71 -65
  42. package/dist/{createFaceAvatar-BlcTYgJv.d.ts → createFaceAvatar-DXgmQmQy.d.ts} +1 -1
  43. package/dist/cross-document-data-match.d.ts +1 -1
  44. package/dist/cross-document-data-match.esm.js +1 -1
  45. package/dist/{crossDocumentDataMatchManager-9XhFVZ5S.esm.js → crossDocumentDataMatchManager-BHnGRsto.esm.js} +4 -4
  46. package/dist/{crossDocumentDataMatchManager-Dq8Z3SOg.d.ts → crossDocumentDataMatchManager-yCWCOzcU.d.ts} +2 -2
  47. package/dist/curp-validation.d.ts +1 -1
  48. package/dist/curp-validation.esm.js +2 -2
  49. package/dist/{curpValidationManager-DNuZn9fn.d.ts → curpValidationManager-B-1EZbP_.d.ts} +3 -3
  50. package/dist/{curpValidationManager-DbIMYFcJ.esm.js → curpValidationManager-DBNIn8BF.esm.js} +15 -22
  51. package/dist/{curpValidationStateMachine-g5kMfuLj.esm.js → curpValidationStateMachine-DAxImy8p.esm.js} +4 -4
  52. package/dist/custom-fields.d.ts +1 -1
  53. package/dist/custom-fields.esm.js +1 -1
  54. package/dist/custom-watchlist.d.ts +1 -1
  55. package/dist/custom-watchlist.esm.js +2 -2
  56. package/dist/{customFieldsManager-CzWZTkW-.esm.js → customFieldsManager-BV6UyLFV.esm.js} +4 -4
  57. package/dist/{customFieldsManager-8-W2KY4n.d.ts → customFieldsManager-CF-cO8ap.d.ts} +2 -2
  58. package/dist/{customWatchlistManager-DWTED5Ur.esm.js → customWatchlistManager-DML0UPzf.esm.js} +3 -3
  59. package/dist/{customWatchlistManager-PBWHr_oM.d.ts → customWatchlistManager-DolUswyR.d.ts} +2 -2
  60. package/dist/{customWatchlistStateMachine-DOQou6TW.esm.js → customWatchlistStateMachine-D9gI5uu7.esm.js} +3 -3
  61. package/dist/deepsightLoader-DvfAYnzg.esm.js +55 -0
  62. package/dist/{deepsightLoader-v-dLejS-.esm.js → deepsightService-DLGO9yv1.esm.js} +51 -56
  63. package/dist/device.esm.js +1 -1
  64. package/dist/document-capture.d.ts +2 -2
  65. package/dist/document-capture.esm.js +2 -2
  66. package/dist/{documentCaptureManager-BRPxqYD2.esm.js → documentCaptureManager-C9XUnYU6.esm.js} +17 -18
  67. package/dist/{documentCaptureManager-lEtCM320.d.ts → documentCaptureManager-DkyDtm3Y.d.ts} +11 -34
  68. package/dist/{documentCaptureStateMachine-D312sSIu.esm.js → documentCaptureStateMachine-BHDsCfhL.esm.js} +55 -58
  69. package/dist/dynamic-forms.d.ts +2 -2
  70. package/dist/dynamic-forms.esm.js +2 -2
  71. package/dist/{dynamicFormsManager-C-9Jhnd3.esm.js → dynamicFormsManager-5uIXobxi.esm.js} +3 -3
  72. package/dist/{dynamicFormsManager-D5R_uxLQ.d.ts → dynamicFormsManager-C0dHs1ty.d.ts} +3 -3
  73. package/dist/{dynamicFormsStateMachine-DGI_F2PP.esm.js → dynamicFormsStateMachine-DQmok084.esm.js} +4 -4
  74. package/dist/ekyb.d.ts +5 -5
  75. package/dist/ekyb.esm.js +3 -3
  76. package/dist/{ekybManager-BwRuGAwt.d.ts → ekybManager-BaZ_7CEu.d.ts} +4 -4
  77. package/dist/{ekybManager-DeoMcZEz.esm.js → ekybManager-CazShiQz.esm.js} +23 -27
  78. package/dist/{ekybStateMachine-D1IJ1Z72.esm.js → ekybStateMachine-hstiCNmW.esm.js} +338 -13
  79. package/dist/ekyc.d.ts +2 -2
  80. package/dist/ekyc.esm.js +3 -3
  81. package/dist/{ekycManager-BX2d46FY.esm.js → ekycManager-BdJRUsz0.esm.js} +29 -36
  82. package/dist/{ekycManager-DLzDuBzI.d.ts → ekycManager-DW-ynodd.d.ts} +3 -3
  83. package/dist/ekycStateMachine-x6Klot8g.esm.js +1444 -0
  84. package/dist/{ekycUtils-D6wfSpPN.esm.js → ekycUtils-zY8CdKJL.esm.js} +53 -3
  85. package/dist/electronic-signature.d.ts +1 -1
  86. package/dist/electronic-signature.esm.js +1 -1
  87. package/dist/{electronicSignatureManager-yYUc30na.esm.js → electronicSignatureManager-Bc8spG8p.esm.js} +10 -10
  88. package/dist/{electronicSignatureManager-Ds-KEVTq.d.ts → electronicSignatureManager-DTSLOvKR.d.ts} +2 -3
  89. package/dist/email.d.ts +1 -1
  90. package/dist/email.esm.js +2 -2
  91. package/dist/{emailManager-bR1N5rV_.esm.js → emailManager-CtlKRRaR.esm.js} +20 -26
  92. package/dist/{emailManager-CADXW_9X.d.ts → emailManager-Dy6omJVp.d.ts} +2 -2
  93. package/dist/{emailStateMachine-Bc_6oIYw.esm.js → emailStateMachine-5Neq3rE5.esm.js} +4 -4
  94. package/dist/{endpoints-B-3Q-LUw.esm.js → endpoints-DHzlkQNe.esm.js} +2 -0
  95. package/dist/{events-ijlbX_Wx.esm.js → events-CAgFE-k3.esm.js} +19 -7
  96. package/dist/events.d.ts +3 -2
  97. package/dist/events.esm.js +1 -1
  98. package/dist/extensibility.d.ts +38 -38
  99. package/dist/extensibility.esm.js +36 -36
  100. package/dist/{externalFetch-BKBThAUC.esm.js → externalFetch-DrR9xshk.esm.js} +3 -2
  101. package/dist/face-match.d.ts +1 -1
  102. package/dist/face-match.esm.js +2 -2
  103. package/dist/{faceCaptureManagerFactory-m-7ae67q.d.ts → faceCaptureManagerFactory-jKQ2QtY-.d.ts} +11 -51
  104. package/dist/{faceCaptureManagerFactory-B6i9QcAk.esm.js → faceCaptureManagerFactory-uFm1AcaY.esm.js} +19 -54
  105. package/dist/{faceCaptureSetup-DU1mjRr6.esm.js → faceCaptureSetup-Cu2wabzo.esm.js} +99 -395
  106. package/dist/{faceMatchManager-Bmv0HzuY.esm.js → faceMatchManager-9Nj9aPci.esm.js} +3 -3
  107. package/dist/{faceMatchManager-BnvrZM2F.d.ts → faceMatchManager-DIgG-UFS.d.ts} +1 -2
  108. package/dist/{faceMatchStateMachine-mRIHQ3dA.esm.js → faceMatchStateMachine-B9ZUnlyU.esm.js} +6 -4
  109. package/dist/{feature-management-C8CuN0os.esm.js → feature-management-BTbYruNe.esm.js} +3 -3
  110. package/dist/feature-management.esm.js +1 -1
  111. package/dist/field-comparison.d.ts +2 -2
  112. package/dist/field-comparison.esm.js +1 -1
  113. package/dist/{fieldComparisonManager-CBvEGxFx.d.ts → fieldComparisonManager-CUX4gza2.d.ts} +6 -3
  114. package/dist/{fieldComparisonManager-CC-klG0h.esm.js → fieldComparisonManager-Dk38hXZP.esm.js} +35 -17
  115. package/dist/fiscal-qr.d.ts +1 -1
  116. package/dist/fiscal-qr.esm.js +1 -1
  117. package/dist/{fiscalQrManager-B1STmT6z.d.ts → fiscalQrManager-D0Iy7EKR.d.ts} +2 -2
  118. package/dist/fiscalQrManager-DmBkQfs0.esm.js +6798 -0
  119. package/dist/flow-events.d.ts +1 -1
  120. package/dist/flow.d.ts +14 -13
  121. package/dist/flow.esm.js +14 -14
  122. package/dist/{flowCompletionService-DccgtDnm.esm.js → flowCompletionService-xnn_SlJU.esm.js} +2 -2
  123. package/dist/{flowServices-CdsWsppG.esm.js → flowServices-DA64AEZC.esm.js} +11 -7
  124. package/dist/geolocation.d.ts +1 -1
  125. package/dist/geolocation.esm.js +2 -2
  126. package/dist/{geolocationManager-CGHBxtMb.esm.js → geolocationManager-CqNLY45e.esm.js} +6 -7
  127. package/dist/{geolocationManager-Cz744E5S.d.ts → geolocationManager-Dn24g1fl.d.ts} +2 -3
  128. package/dist/{geolocationStateMachine-CdEb1Gn0.esm.js → geolocationStateMachine-D-cQi7ar.esm.js} +4 -4
  129. package/dist/{getDeviceClass-5NYZg9s6.esm.js → getDeviceClass-bsWdEDxP.esm.js} +1 -1
  130. package/dist/{getParsedUserAgent-B2iFDql1.esm.js → getParsedUserAgent-DhQPeKqw.esm.js} +1 -1
  131. package/dist/government-validation.d.ts +1 -1
  132. package/dist/government-validation.esm.js +2 -2
  133. package/dist/{governmentValidationManager-D02vT4yY.d.ts → governmentValidationManager-B82zOut8.d.ts} +3 -3
  134. package/dist/{governmentValidationManager-CacFOtKO.esm.js → governmentValidationManager-CP0SiOOe.esm.js} +3 -3
  135. package/dist/{governmentValidationStateMachine-CoaZOSTP.esm.js → governmentValidationStateMachine-C3YQ8yCZ.esm.js} +3 -3
  136. package/dist/home.d.ts +3 -26
  137. package/dist/home.esm.js +2 -2
  138. package/dist/id-ocr.d.ts +1 -1
  139. package/dist/id-ocr.esm.js +2 -2
  140. package/dist/id-selfie-flow.d.ts +6 -9
  141. package/dist/id-selfie-flow.esm.js +23 -28
  142. package/dist/id-verification.d.ts +1 -1
  143. package/dist/id-verification.esm.js +2 -2
  144. package/dist/id.d.ts +4 -4
  145. package/dist/id.esm.js +4 -205
  146. package/dist/{idCaptureDefaults-Dl64Trv5.esm.js → idCaptureDefaults-CBg73RnV.esm.js} +8 -5
  147. package/dist/{idCaptureManager-BHwL2Z0V.d.ts → idCaptureManager-BY97SRCV.d.ts} +242 -98
  148. package/dist/{idCaptureManager-BNmVJQkd.esm.js → idCaptureManager-D4V8vXtY.esm.js} +276 -210
  149. package/dist/{idCaptureServices-dtDG2HXS.esm.js → idCaptureServices-DJ33k3-p.esm.js} +21 -39
  150. package/dist/{idCaptureStateMachine-BbiZD3uf.esm.js → idCaptureStateMachine-PJt0yzom.esm.js} +989 -872
  151. package/dist/{idOcrManager-CFlDwUvN.d.ts → idOcrManager-BTdHlJ9D.d.ts} +2 -3
  152. package/dist/{idOcrManager-ByWuQ4ej.esm.js → idOcrManager-DxoCesYP.esm.js} +3 -3
  153. package/dist/{idOcrStateMachine-Bt_abXQQ.esm.js → idOcrStateMachine-kPn7DB2I.esm.js} +3 -3
  154. package/dist/{idVerificationManager-BB4Pj_N6.d.ts → idVerificationManager-BJapZfpy.d.ts} +2 -3
  155. package/dist/{idVerificationManager-DT42c77H.esm.js → idVerificationManager-CDhRUU5n.esm.js} +3 -3
  156. package/dist/{idVerificationStateMachine-DbDVIh9t.esm.js → idVerificationStateMachine-0HTVVxse.esm.js} +3 -3
  157. package/dist/identity-reuse.d.ts +2 -2
  158. package/dist/identity-reuse.esm.js +2 -2
  159. package/dist/{identityReuseManager-yG3acUrQ.esm.js → identityReuseManager-BleUi6TK.esm.js} +26 -13
  160. package/dist/{identityReuseManager-BXEBBI3N.d.ts → identityReuseManager-ooAbp6Kc.d.ts} +164 -46
  161. package/dist/{identityReuseStateMachine-BhAkuIra.esm.js → identityReuseStateMachine-BrBUpfn5.esm.js} +89 -23
  162. package/dist/{index-CeaC32qI.d.ts → index-DfEuu-Ew.d.ts} +2 -2
  163. package/dist/index-DpTj5PFb.d.ts +1 -0
  164. package/dist/index.d.ts +1 -1
  165. package/dist/index.esm.js +1 -1
  166. package/dist/{invokeOnCaptureCallback-BjzPjJ_p.esm.js → invokeOnCaptureCallback-BW7V3vub.esm.js} +1 -1
  167. package/dist/lamejs-B6TzhU5q.esm.js +10 -0
  168. package/dist/mandatory-consent.d.ts +2 -2
  169. package/dist/mandatory-consent.esm.js +2 -2
  170. package/dist/{mandatoryConsentManager-7dI0D08q.esm.js → mandatoryConsentManager-CX_Dyht7.esm.js} +3 -3
  171. package/dist/{mandatoryConsentManager-DFVm_i2B.d.ts → mandatoryConsentManager-TkX57U6f.d.ts} +3 -4
  172. package/dist/{mandatoryConsentStateMachine-DC5Mo6M1.esm.js → mandatoryConsentStateMachine-CoaSB7P7.esm.js} +3 -3
  173. package/dist/{multipartRecordingService-mwKFTlhR.esm.js → multipartRecordingService-DbeQpXdU.esm.js} +2 -2
  174. package/dist/{permissionGuards-D6TfVop2.esm.js → permissionGuards-Bf4LbHjC.esm.js} +1 -1
  175. package/dist/{permissionServices-EY3_Vx6c.esm.js → permissionServices-d2UW0r_L.esm.js} +34 -3
  176. package/dist/phone-number.d.ts +24 -0
  177. package/dist/phone-number.esm.js +1922 -0
  178. package/dist/phone.d.ts +1 -1
  179. package/dist/phone.esm.js +2 -2
  180. package/dist/{phoneManager-IjhSv5XS.esm.js → phoneManager-BGBcpqsk.esm.js} +31 -22
  181. package/dist/{phoneManager-BBU8Lq0O.d.ts → phoneManager-BO-LTih5.d.ts} +16 -3
  182. package/dist/{phoneStateMachine-CXdXufKg.esm.js → phoneStateMachine-CEm2y7yG.esm.js} +39 -20
  183. package/dist/phoneValidation-DnBcNUNh.esm.js +48 -0
  184. package/dist/qe-signature.d.ts +2 -2
  185. package/dist/qe-signature.esm.js +1 -1
  186. package/dist/redirect-to-mobile.d.ts +1 -1
  187. package/dist/redirect-to-mobile.esm.js +2 -2
  188. package/dist/{redirectToMobileManager-FLEWJtv8.esm.js → redirectToMobileManager-B1yQHFLu.esm.js} +4 -4
  189. package/dist/{redirectToMobileManager-BD2PvT5B.d.ts → redirectToMobileManager-CVsvpVbQ.d.ts} +2 -2
  190. package/dist/{redirectToMobileStateMachine-CSWWgUTO.esm.js → redirectToMobileStateMachine--Sr9NEVT.esm.js} +5 -5
  191. package/dist/{rolldown-runtime-FDOR9p9I.esm.js → rolldown-runtime-B-1-B7_t.esm.js} +10 -1
  192. package/dist/{runChildModule-B2Y_hlix.esm.js → runChildModule-vdxQMCmO.esm.js} +13 -12
  193. package/dist/safeStorage-UxDrnjwa.esm.js +35 -0
  194. package/dist/{sdkVersion-N1UiyFiY.esm.js → sdkVersion-F5YZbjip.esm.js} +1 -1
  195. package/dist/selfie.d.ts +4 -4
  196. package/dist/selfie.esm.js +12 -12
  197. package/dist/selfieErrorUtils-D-tsgEuX.esm.js +21 -0
  198. package/dist/{selfieManager-BSZG891S.d.ts → selfieManager-1J0IEyFq.d.ts} +2 -8
  199. package/dist/{selfieManager-AS6wfUvZ.esm.js → selfieManager-26pENXxw.esm.js} +6 -6
  200. package/dist/selfieStateMachine-BhfKsl3I.esm.js +41 -0
  201. package/dist/{session-BESZc05k.esm.js → session-Bu7djCsj.esm.js} +5 -5
  202. package/dist/session.d.ts +15 -3
  203. package/dist/session.esm.js +30 -5
  204. package/dist/sessionInitializer-jxbfCnE-.esm.js +974 -0
  205. package/dist/{setup-BuPbxpwF.esm.js → setup-C38QkrSP.esm.js} +232 -112
  206. package/dist/{setup-Cyko6cAn.d.ts → setup-lgkQ8dJW.d.ts} +20 -11
  207. package/dist/signature.d.ts +1 -1
  208. package/dist/signature.esm.js +2 -2
  209. package/dist/{signatureManager-DGBSWz2_.d.ts → signatureManager-BpVt_Jv7.d.ts} +2 -2
  210. package/dist/{signatureManager-B72np4ZO.esm.js → signatureManager-qqXyH93e.esm.js} +7 -11
  211. package/dist/{signatureStateMachine-ce_XMA3l.esm.js → signatureStateMachine-BkRSrkKp.esm.js} +3 -3
  212. package/dist/splitTrackedName-3zJRd2RD.esm.js +19 -0
  213. package/dist/{src-35ekGRzK.esm.js → src-DJAJjRYQ.esm.js} +1 -1
  214. package/dist/{stats-CZcy2J53.esm.js → stats-DmG_tkGk.esm.js} +1 -1
  215. package/dist/stats.esm.js +1 -1
  216. package/dist/{sessionInitializer-CnhAkMUN.esm.js → thumbmark.esm-CRc0cYvz.esm.js} +33 -776
  217. package/dist/{trueSightService-M5VpCo96.esm.js → trueSightService-BWgRVGIT.esm.js} +893 -900
  218. package/dist/trust-graph.d.ts +1 -2
  219. package/dist/trust-graph.esm.js +2 -2
  220. package/dist/{types-C0oWGUzg.d.ts → types-0L-eFDmr.d.ts} +1 -1
  221. package/dist/{types-C1g9itHE.d.ts → types-BGXxRwQM.d.ts} +6 -7
  222. package/dist/{types-BfgmV7s7.d.ts → types-BQA7lRDV.d.ts} +1 -1
  223. package/dist/{types-CxGdmzxa.d.ts → types-BvFmYX5c.d.ts} +1 -1
  224. package/dist/{types-Cx-1zo-i.d.ts → types-BvLo3U4u.d.ts} +1 -1
  225. package/dist/types-C71Zo6vl.d.ts +6 -0
  226. package/dist/{types-DEGVXwsk2.d.ts → types-CyPUIpzf.d.ts} +110 -62
  227. package/dist/{types-Bx-BWveD.d.ts → types-DTYr9pCB.d.ts} +1 -1
  228. package/dist/{types-BjSly8cp.d.ts → types-DUnFZ08M.d.ts} +1 -1
  229. package/dist/{types-ESdtnHtg.d.ts → types-zpO4fU6a.d.ts} +3 -4
  230. package/dist/{urlUtils-D6phjq1-.esm.js → urlUtils-6jsE6rKp.esm.js} +9 -1
  231. package/dist/video-selfie.d.ts +6 -9
  232. package/dist/video-selfie.esm.js +1386 -1344
  233. package/dist/{videoSelfieCameraWarmer-q9ZjvXu9.esm.js → videoSelfieCameraWarmer-C366RKqu.esm.js} +11 -11
  234. package/dist/{videoSelfieFlowModuleMachine-BUe2uACW.esm.js → videoSelfieFlowModuleMachine-BCFVHBQ1.esm.js} +1 -1
  235. package/dist/{videoSelfieStepDetector-ChYgFM15.d.ts → videoSelfieStepDetector-C5QHOvWb.d.ts} +4 -4
  236. package/dist/videoTrimByCopy-BFfAiNpv.esm.js +15901 -0
  237. package/dist/{warmup-Cis2p6O0.d.ts → warmup-BUPPRAA1.d.ts} +7 -0
  238. package/dist/{warmup-ChDwHSel.esm.js → warmup-DMtbyb2l.esm.js} +27 -9
  239. package/dist/wasm.d.ts +2 -2
  240. package/dist/wasm.esm.js +4 -4
  241. package/dist/{wasmDsFlagSync-DYrLUEII.esm.js → wasmDsFlagSync-BXCGqP48.esm.js} +1 -1
  242. package/dist/{wasmWebClient-B7baE-Wf.esm.js → wasmWebClient-kbYZ2sxw.esm.js} +259 -20
  243. package/dist/watchlist-for-business.d.ts +1 -1
  244. package/dist/watchlist-for-business.esm.js +2 -2
  245. package/dist/watchlist.d.ts +1 -1
  246. package/dist/watchlist.esm.js +2 -2
  247. package/dist/{watchlistForBusinessManager-CsnJnG-C.d.ts → watchlistForBusinessManager-B3GDPZrr.d.ts} +2 -2
  248. package/dist/{watchlistForBusinessManager-CIMkpEhu.esm.js → watchlistForBusinessManager-BvF4jK3S.esm.js} +7 -5
  249. package/dist/{watchlistForBusinessStateMachine-CJ-MvUre.esm.js → watchlistForBusinessStateMachine-121XK-Et.esm.js} +3 -3
  250. package/dist/{watchlistManager-CixcLNDT.esm.js → watchlistManager-CNWEOYSq.esm.js} +3 -3
  251. package/dist/{watchlistManager-DLXef90v.d.ts → watchlistManager-DJQOd8Pw.d.ts} +2 -2
  252. package/dist/{watchlistStateMachine-CBqk2w-h.esm.js → watchlistStateMachine-Dgfm42w2.esm.js} +3 -3
  253. package/dist/workflow.d.ts +34 -5
  254. package/dist/workflow.esm.js +64 -46
  255. package/dist/{xstate-actors.esm-BXeQ-Ucz.esm.js → xstate-actors.esm-8Gzepa_A.esm.js} +1 -1
  256. package/dist/{xstate.esm-BuFLFuzz.esm.js → xstate.esm-DSajW7CJ.esm.js} +24 -10
  257. package/package.json +13 -5
  258. package/dist/Actor-iTq7YY48.d.ts +0 -1
  259. package/dist/StateMachine-EDpYWy0i.d.ts +0 -6
  260. package/dist/fiscalQrManager-DbCVMIBx.esm.js +0 -34143
  261. package/dist/index-B8weJxmL.d.ts +0 -1
  262. package/dist/selfieErrorUtils-UiQ46EBa.esm.js +0 -116
  263. package/dist/selfieStateMachine-DY7vJVNY.esm.js +0 -71
  264. package/dist/types-BN5RPPIZ.esm.js +0 -34
  265. package/dist/types-Bt9RuRqt.d.ts +0 -6
  266. package/dist/virtualCameraCheck-Cjab7FTI.esm.js +0 -48
  267. /package/dist/{types-BSBzhHRH.d.ts → types-BSBzhHRH2.d.ts} +0 -0
@@ -1,31 +1,33 @@
1
- import { c as trueSightHintTransition, l as trueSightLog, m as isTrueSightTesting, r as noteTrueSightCaptureActivity } from "./trueSightService-M5VpCo96.esm.js";
2
- import { r as WasmUtilProvider } from "./safeStorage-BdU9_m2E.esm.js";
3
- import { l as BrowserStorageProvider } from "./setup-BuPbxpwF.esm.js";
4
- import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
5
- import { s as exposeExperimentOnce } from "./feature-management-C8CuN0os.esm.js";
1
+ import { c as trueSightHintTransition, l as trueSightLog, r as noteTrueSightCaptureActivity, v as isTrueSightTesting } from "./trueSightService-BWgRVGIT.esm.js";
2
+ import { t as WasmUtilProvider } from "./WasmUtilProvider-CIdnE7Fx.esm.js";
6
3
  import { n as revokeObjectURL, t as getTimestamp } from "./IBrowserEnvironmentCapability-DEaRRk-R.esm.js";
4
+ import { n as eventModuleNames, o as createManagerInstrumentation, s as recordExperimentAssignmentOnce, v as addEvent } from "./events-CAgFE-k3.esm.js";
7
5
  import { r as getToken, t as api } from "./api-Bx3gKW_y.esm.js";
8
- import { n as eventModuleNames, s as recordExperimentAssignmentOnce } from "./events-ijlbX_Wx.esm.js";
9
- import { t as getDisableIpify } from "./sessionInitializer-CnhAkMUN.esm.js";
10
- import { t as endpoints } from "./endpoints-B-3Q-LUw.esm.js";
11
- import { t as addDeviceStats } from "./stats-CZcy2J53.esm.js";
6
+ import { l as isGoogleWalletSignedRequestsEnabled, u as BrowserStorageProvider } from "./setup-C38QkrSP.esm.js";
7
+ import { t as endpoints } from "./endpoints-DHzlkQNe.esm.js";
8
+ import { t as getDisableIpify } from "./sessionInitializer-jxbfCnE-.esm.js";
9
+ import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
10
+ import { s as exposeExperimentOnce } from "./feature-management-BTbYruNe.esm.js";
11
+ import { t as addDeviceStats } from "./stats-DmG_tkGk.esm.js";
12
12
  import { a as isIOS, s as isIPad, t as isAndroid, u as isMacOsSafari } from "./platform-DkcRc9JV.esm.js";
13
- import "./session-BESZc05k.esm.js";
14
- import { c as not, i as assign, l as raise, n as enqueueActions, o as and, r as sendTo, t as setup } from "./xstate.esm-BuFLFuzz.esm.js";
15
- import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-BXeQ-Ucz.esm.js";
16
- import { n as getUrlSearchParam, t as buildUrlWithSearchParams } from "./urlUtils-D6phjq1-.esm.js";
13
+ import "./session-Bu7djCsj.esm.js";
14
+ import { c as not, i as assign, l as raise, n as enqueueActions, o as and, r as sendTo, s as createActor, t as setup, u as createManager } from "./xstate.esm-DSajW7CJ.esm.js";
15
+ import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-8Gzepa_A.esm.js";
16
+ import { n as getUrlSearchParam, t as buildUrlWithSearchParams } from "./urlUtils-6jsE6rKp.esm.js";
17
17
  import { r as getCameraReacquisitionConstraints } from "./camera-sIa9ceW2.esm.js";
18
- import { n as invokeRequestPermissionActor, t as checkPermission } from "./permissionServices-EY3_Vx6c.esm.js";
19
- import { i as imageFileToDataUrl, n as runProtectedAttempt, o as flagIdManualReview, r as cameraCycleProvider, s as PROTECTED_CAPTURE_TARGET, t as createCameraCycleOwnership } from "./createCameraCycleOwnership-DnwTl1ad.esm.js";
18
+ import { i as requestMotionPermission, n as invokeRequestPermissionActor, o as requiresMotionPermissionUserGesture, t as checkPermission } from "./permissionServices-d2UW0r_L.esm.js";
19
+ import { a as flagIdManualReview, n as runProtectedAttempt, o as PROTECTED_CAPTURE_TARGET, r as cameraCycleProvider, t as createCameraCycleOwnership } from "./createCameraCycleOwnership-kdlhkmmY.esm.js";
20
20
  import { t as IncodeCanvas } from "./canvas-BxFzWTt-.esm.js";
21
- import { n as needsMotionPrimeOnlyFromPermission, r as resolveStoredPermissionResult, t as isReadyForCaptureFromPermission } from "./permissionGuards-D6TfVop2.esm.js";
22
- import { o as StreamCanvasProcessingSession } from "./multipartRecordingService-mwKFTlhR.esm.js";
21
+ import { n as needsMotionPrimeOnlyFromPermission, r as resolveStoredPermissionResult, t as isReadyForCaptureFromPermission } from "./permissionGuards-Bf4LbHjC.esm.js";
22
+ import { o as StreamCanvasProcessingSession } from "./multipartRecordingService-DbeQpXdU.esm.js";
23
23
  import { t as sleep } from "./ITimerCapability-DgdDzp8G.esm.js";
24
24
  import { t as getBrowser } from "./getBrowser-Cbe1C4Tn.esm.js";
25
+ import { a as isBackCameraLabel } from "./backCameraStream-CMvXj4X4.esm.js";
25
26
  import { n as getWindowDimensions } from "./getDeviceClass-CeBiEayS.esm.js";
26
- import { n as runVirtualCameraCheck, t as frameSources } from "./virtualCameraCheck-Cjab7FTI.esm.js";
27
- import { n as DEFAULT_ID_CAPTURE_THRESHOLDS, r as IdCaptureProvider } from "./idCaptureDefaults-Dl64Trv5.esm.js";
28
- import { C as getIdCameraStream, S as OPTIMIZING_WATCHDOG_MARGIN_MS, _ as validateUploadResponse, a as getRealQualityValue, b as resolveIdCaptureThresholds, c as pauseRecordingForAttempt, d as resumeRecordingAfterPause, f as startRecordingSession, g as uploadManualIdFile, h as uploadIdImage, i as finalizeRecordingSession, l as prepareRecordingForCameraRetry, m as stopStream, o as initializeIdCapture, p as stopRecording, r as disposeRecordingSession, s as isSuspendableRecordingSession, t as addBarcode, u as processId, v as ID_ERROR_CODES, x as CAPTURE_OPTIMIZING } from "./idCaptureServices-dtDG2HXS.esm.js";
27
+ import { n as frameSources, r as runVirtualCameraCheck } from "./deepsightService-DLGO9yv1.esm.js";
28
+ import { t as getDeviceClass } from "./getDeviceClass-bsWdEDxP.esm.js";
29
+ import { n as DEFAULT_ID_CAPTURE_THRESHOLDS, r as IdCaptureProvider } from "./idCaptureDefaults-CBg73RnV.esm.js";
30
+ import { C as getIdCameraStream, S as OPTIMIZING_WATCHDOG_MARGIN_MS, _ as ID_ERROR_CODES, a as getRealQualityValue, b as resolveIdCaptureThresholds, c as pauseRecordingForAttempt, d as resumeRecordingAfterPause, f as startRecordingSession, g as validateUploadResponse, h as uploadIdImage, i as finalizeRecordingSession, l as prepareRecordingForCameraRetry, m as stopStream, o as initializeIdCapture, p as stopRecording, r as disposeRecordingSession, s as isSuspendableRecordingSession, t as addBarcode, u as processId, x as CAPTURE_OPTIMIZING, y as resolveCountdownStepMs } from "./idCaptureServices-DJ33k3-p.esm.js";
29
31
  import { t as StreamCanvasCapture } from "./StreamCanvasCapture-C9jtgBdT.esm.js";
30
32
  //#region src/modules/id/digitalUpload/digitalUploadErrors.ts
31
33
  const TERMINAL_FAIL_REASONS = /* @__PURE__ */ new Set(["DIGITAL_ID_REQUESTED_BUT_OTHER_PROVIDED", "ID_TYPE_UNACCEPTABLE"]);
@@ -89,7 +91,7 @@ const uploadDigitalIdActor = fromPromise(async ({ input, signal }) => {
89
91
  extractIdFace: input.extractIdFace
90
92
  });
91
93
  });
92
- const getUploadResponseFromEvent$1 = (event) => {
94
+ const getUploadResponseFromEvent = (event) => {
93
95
  if (!("output" in event)) return;
94
96
  return event.output;
95
97
  };
@@ -153,10 +155,10 @@ const digitalUploadMachine = setup({
153
155
  isSelectedFileTooLarge: ({ event }) => event.type === "FILE_PICKED" && event.file.size > 5242880,
154
156
  isSelectedFileMimeInvalid: ({ event }) => event.type === "FILE_PICKED" && !DIGITAL_UPLOAD_ALLOWED_MIME_TYPES.includes(event.file.type),
155
157
  hasSelectedFile: ({ context }) => context.file !== null,
156
- responseHasFailReason: ({ event }) => mapDigitalIdFailReason(getUploadResponseFromEvent$1(event)) !== null,
158
+ responseHasFailReason: ({ event }) => mapDigitalIdFailReason(getUploadResponseFromEvent(event)) !== null,
157
159
  hasAttemptsRemaining: ({ context }) => context.attemptsRemaining > 0,
158
160
  isTerminalFailReason: ({ event }) => {
159
- return isTerminalDigitalIdFailReason(getUploadResponseFromEvent$1(event));
161
+ return isTerminalDigitalIdFailReason(getUploadResponseFromEvent(event));
160
162
  },
161
163
  isCurrentFailReasonTerminal: ({ context }) => {
162
164
  const terminalReasons = /* @__PURE__ */ new Set(["DIGITAL_ID_REQUESTED_BUT_OTHER_PROVIDED", "ID_TYPE_UNACCEPTABLE"]);
@@ -316,14 +318,39 @@ const digitalUploadMachine = setup({
316
318
  //#region src/modules/id/deviceWallet/deviceWalletServices.ts
317
319
  /** Both wallet endpoint families are versioned via header, not path. */
318
320
  const API_VERSION_HEADERS = { "api-version": "1.0" };
321
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
319
322
  /**
320
- * Fetches the wallet request payload for the platform and wraps it in the
321
- * Digital Credentials API request shape.
323
+ * Turns the Google request payload into DC API request options without ever
324
+ * touching the payload itself — a signed request's JWS is covered by its
325
+ * signature, so any edit makes the wallet reject it. The backend decides the
326
+ * protocol; the SDK never derives it.
327
+ *
328
+ * - `{ requests: [...] }` — full DC API envelope (signed-capable backend):
329
+ * passed through as-is.
330
+ * - `{ protocol, data }` — an already-enveloped single request: not re-wrapped,
331
+ * only placed in the `requests` array (defensive, for rollout shape drift).
332
+ * - anything else — legacy bare unsigned OIDC4VP/DCQL object: wrapped with
333
+ * `openid4vp-v1-unsigned`.
334
+ */
335
+ function toGoogleRequestOptions(payload) {
336
+ if (isRecord(payload) && Array.isArray(payload.requests)) return payload;
337
+ if (isRecord(payload) && typeof payload.protocol === "string") return { requests: [payload] };
338
+ return { requests: [{
339
+ protocol: "openid4vp-v1-unsigned",
340
+ data: payload
341
+ }] };
342
+ }
343
+ /**
344
+ * Fetches the wallet request payload for the platform as Digital Credentials
345
+ * API request options.
322
346
  *
323
347
  * Apple: POST with an empty body → `{ deviceRequest, encryptionInfo }`,
324
348
  * presented with protocol `org-iso-mdoc`.
325
- * Google: GET → the whole backend-built OIDC4VP/DCQL object (opaque here),
326
- * presented with protocol `openid4vp-v1-unsigned`.
349
+ * Google: GET with `signedSupported=true` → the backend-built DC API envelope
350
+ * (signed or unsigned, backend's choice), passed through verbatim — see
351
+ * {@link toGoogleRequestOptions}. With `setup({ deviceWallet: {
352
+ * googleSignedRequests: false } })` the SDK sends `signedSupported=false` and
353
+ * the backend answers with the legacy bare unsigned payload.
327
354
  */
328
355
  async function requestWalletData(platform, signal) {
329
356
  if (platform === "ios") {
@@ -333,23 +360,21 @@ async function requestWalletData(platform, signal) {
333
360
  });
334
361
  if (!res.ok) throw new Error(`POST ${endpoints.deviceWalletAppleRequest} failed: ${res.status}`);
335
362
  const data = res.data;
336
- return {
363
+ return { requests: [{
337
364
  protocol: "org-iso-mdoc",
338
365
  data: {
339
366
  deviceRequest: data.deviceRequest,
340
367
  encryptionInfo: data.encryptionInfo
341
368
  }
342
- };
369
+ }] };
343
370
  }
344
371
  const res = await api.get(endpoints.deviceWalletGoogleRequest, {
345
372
  headers: API_VERSION_HEADERS,
373
+ query: { signedSupported: isGoogleWalletSignedRequestsEnabled() },
346
374
  signal
347
375
  });
348
376
  if (!res.ok) throw new Error(`GET ${endpoints.deviceWalletGoogleRequest} failed: ${res.status}`);
349
- return {
350
- protocol: "openid4vp-v1-unsigned",
351
- data: res.data
352
- };
377
+ return toGoogleRequestOptions(res.data);
353
378
  }
354
379
  /**
355
380
  * Forwards the opaque wallet response for backend decryption. The body key
@@ -529,408 +554,221 @@ function resolveWalletPlatform(provider) {
529
554
  return isIOS() || isMacOsSafari() || isIPad() ? "ios" : "android";
530
555
  }
531
556
  //#endregion
532
- //#region src/modules/id/manualUpload/manualUploadErrors.ts
557
+ //#region src/modules/id/deviceWallet/instrumentation.ts
533
558
  /**
534
- * i18n keys for manual-upload user-facing error copy.
535
- * Keep in sync with `packages/ui/src/i18n/**.json` under the `manualIdUpload.*` namespace.
559
+ * Maps a device-wallet child-machine snapshot to a tracked screen name.
560
+ * Family is `deviceWallet.*` — emitted cross-family under the parent
561
+ * `idCapture` module token, same as `mapDigitalIdScreenName`. See
562
+ * `docs/patterns/INSTRUMENTATION.md`.
536
563
  */
537
- const MANUAL_UPLOAD_ERROR_KEYS = {
538
- wrongSide: "manualIdUpload.wrongSide",
539
- wrongDocument: "manualIdUpload.wrongDocument",
540
- fileTooBig: "manualIdUpload.fileTooBig",
541
- generic: "manualIdUpload.generic",
542
- lowSharpness: "manualIdUpload.lowSharpness",
543
- glareDetected: "manualIdUpload.glareDetected",
544
- readabilityIssue: "manualIdUpload.readabilityIssue"
564
+ function mapDeviceWalletScreenName(value, failReason) {
565
+ if (value === "presenting") return "deviceWallet.wallet.default";
566
+ if (value === "failed") {
567
+ if (failReason === "NO_CREDENTIAL") return "deviceWallet.error.nocredential";
568
+ if (failReason === "UNSUPPORTED_PLATFORM") return "deviceWallet.error.unsupported";
569
+ return "deviceWallet.error.default";
570
+ }
571
+ return "deviceWallet.loading.default";
572
+ }
573
+ //#endregion
574
+ //#region ../infra/src/providers/browser/BrowserDigitalCredentialsProvider.ts
575
+ var BrowserDigitalCredentialsProvider = class {
576
+ isAvailable() {
577
+ return typeof navigator !== "undefined" && typeof navigator.credentials?.get === "function" && typeof globalThis.DigitalCredential !== "undefined";
578
+ }
579
+ async get(request, options) {
580
+ return { response: (await navigator.credentials.get({
581
+ mediation: "required",
582
+ signal: options?.signal,
583
+ digital: request
584
+ }))?.data?.response };
585
+ }
545
586
  };
587
+ //#endregion
588
+ //#region src/modules/id/deviceWallet/deviceWalletActor.ts
546
589
  /**
547
- * Map the server's `failReason` (plus client-side passport/ID tab mismatch and
548
- * per-image quality flags) onto an i18n key. Mirrors v1's `shouldUploadAgain`
549
- * branch logic in
550
- * `ms-incodesmile-web/packages/incode-welcome/src/manualIdUpload/manuaIdUpload.tsx`,
551
- * extended to also reject responses where the backend flagged a per-image
552
- * quality issue (`correctSharpness`, `correctGlare`, or `readability`).
553
- * `idQualityAttemptApproved` is intentionally ignored — v1 did not treat it
554
- * as a manual-upload rejection signal.
555
- *
556
- * Quality flags are checked with `=== false` so missing/undefined values
557
- * (older backends, partial payloads) keep the legacy "trust the server"
558
- * behaviour and do not produce false positives.
559
- *
560
- * Returns `null` when the response represents a successful upload — the caller
561
- * should treat `null` as "no error, advance to next slot".
590
+ * Starts a device-wallet actor for headless use. The ID-Capture parent invokes
591
+ * `deviceWalletMachine` itself (and resolves the platform through the
592
+ * digital-ID funnel); this factory is the standalone entry point for
593
+ * integrators driving the wallet handshake on their own.
562
594
  */
563
- function mapFailReasonToErrorKey(response, expectedTab) {
564
- const failReason = response.failReason;
565
- if (failReason === "WRONG_DOCUMENT_SIDE") return MANUAL_UPLOAD_ERROR_KEYS.wrongSide;
566
- if (failReason === "WRONG_ONE_SIDED_DOCUMENT" || failReason === "UNKNOWN_DOCUMENT_TYPE") return MANUAL_UPLOAD_ERROR_KEYS.wrongDocument;
567
- if (failReason === "ID_TYPE_UNACCEPTABLE") return MANUAL_UPLOAD_ERROR_KEYS.wrongDocument;
568
- if (expectedTab === "passport" && response.typeOfId && response.typeOfId.toLowerCase() !== "passport") return MANUAL_UPLOAD_ERROR_KEYS.wrongDocument;
569
- if (expectedTab === "id" && response.typeOfId?.toLowerCase() === "passport") return MANUAL_UPLOAD_ERROR_KEYS.wrongDocument;
570
- if (response.correctSharpness === false) return MANUAL_UPLOAD_ERROR_KEYS.lowSharpness;
571
- if (response.correctGlare === false) return MANUAL_UPLOAD_ERROR_KEYS.glareDetected;
572
- if (response.readability === false) return MANUAL_UPLOAD_ERROR_KEYS.readabilityIssue;
573
- return null;
595
+ function createDeviceWalletActor(options = {}) {
596
+ return createActor(deviceWalletMachine, { input: {
597
+ platform: resolveWalletPlatform(options.provider),
598
+ credentials: options.credentials ?? new BrowserDigitalCredentialsProvider(),
599
+ services: options.services
600
+ } }).start();
574
601
  }
575
602
  //#endregion
576
- //#region src/modules/id/manualUpload/types.ts
577
- const MANUAL_UPLOAD_MAX_RETRIES = 3;
578
- const MANUAL_UPLOAD_MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
579
- const MANUAL_UPLOAD_EXHAUSTION_FORWARD_MS = 5e3;
580
- //#endregion
581
- //#region src/modules/id/manualUpload/manualUploadServices.ts
582
- const stripDataUrlPrefix = (dataUrl) => {
583
- const commaIndex = dataUrl.indexOf(",");
584
- return commaIndex === -1 ? dataUrl : dataUrl.slice(commaIndex + 1);
585
- };
586
- const getBase64PayloadByteLength = (payload) => {
587
- const normalized = payload.replace(/\s/g, "");
588
- let padding = 0;
589
- if (normalized.endsWith("==")) padding = 2;
590
- else if (normalized.endsWith("=")) padding = 1;
591
- return Math.max(0, Math.floor(normalized.length * 3 / 4) - padding);
592
- };
593
- const isBase64PayloadWithinSize = (payload, maxBytes) => getBase64PayloadByteLength(payload) <= maxBytes;
594
- const fileToBase64Payload = async (file) => {
595
- const dataUrl = await imageFileToDataUrl(file);
596
- return stripDataUrlPrefix(dataUrl);
597
- };
598
- //#endregion
599
- //#region src/modules/id/manualUpload/manualUploadStateMachine.ts
600
- const uploadManualFileActor = fromPromise(async ({ input, signal }) => {
601
- const base64Image = await fileToBase64Payload(input.file);
602
- if (!isBase64PayloadWithinSize(base64Image, 5242880)) return { type: "fileTooLarge" };
603
+ //#region src/modules/id/deviceWallet/deviceWalletManager.ts
604
+ /**
605
+ * Absolute tracked-screen name for a device-wallet snapshot. Emitted verbatim
606
+ * on `event.screen` under the parent `idCapture` module token — the wallet is
607
+ * an ID-Capture sub-flow, so it does not own an analytics module of its own
608
+ * (see `docs/patterns/INSTRUMENTATION.md`).
609
+ */
610
+ function getDeviceWalletScreenName(snapshot) {
611
+ return mapDeviceWalletScreenName(snapshot.value, snapshot.context?.failReason ?? null);
612
+ }
613
+ function mapState(snapshot) {
614
+ const typedSnapshot = snapshot;
615
+ const { context } = typedSnapshot;
616
+ const { platform } = context;
617
+ if (typedSnapshot.matches("presenting")) return {
618
+ status: "presenting",
619
+ platform
620
+ };
621
+ if (typedSnapshot.matches("decrypting")) return {
622
+ status: "decrypting",
623
+ platform
624
+ };
625
+ if (typedSnapshot.matches("finished") && context.response) return {
626
+ status: "finished",
627
+ platform,
628
+ decision: context.response.decision,
629
+ response: context.response
630
+ };
631
+ if (typedSnapshot.matches("failed")) return {
632
+ status: "failed",
633
+ platform,
634
+ reason: context.failReason ?? "WALLET_ERROR"
635
+ };
636
+ if (typedSnapshot.matches("closed")) return {
637
+ status: "closed",
638
+ platform
639
+ };
603
640
  return {
604
- type: "uploaded",
605
- response: await uploadManualIdFile({
606
- side: input.side,
607
- base64Image,
608
- retry: input.retry,
609
- onlyFront: input.onlyFront,
610
- isSecondId: input.isSecondId,
611
- extractIdFace: input.extractIdFace,
612
- signal
613
- })
641
+ status: "requesting",
642
+ platform
614
643
  };
615
- });
616
- const getUploadOutputFromEvent = (event) => {
617
- if ("output" in event) return event.output;
618
- };
619
- const getUploadResponseFromEvent = (event) => {
620
- const output = getUploadOutputFromEvent(event);
621
- return output?.type === "uploaded" ? output.response : void 0;
622
- };
623
- const manualUploadMachine = setup({
624
- types: {
625
- context: {},
626
- events: {},
627
- input: {},
628
- output: {}
629
- },
630
- actors: { uploadManualFile: uploadManualFileActor },
631
- actions: {
632
- setManualUploadActiveTab: assign(({ context, event }) => {
633
- if (event.type !== "MANUAL_UPLOAD_TAB_CHANGED") return {};
634
- return {
635
- activeTab: event.tab,
636
- errorMessage: null,
637
- errorSide: null,
638
- retriesLeft: context.captureAttempts
639
- };
640
- }),
641
- resetManualUpload: assign(({ context }) => ({
642
- frontFile: null,
643
- backFile: null,
644
- passportFile: null,
645
- frontUploaded: false,
646
- backUploaded: false,
647
- passportUploaded: false,
648
- skipBackFromServer: false,
649
- errorMessage: null,
650
- errorSide: null,
651
- retriesLeft: context.captureAttempts
652
- })),
653
- storeSelectedFile: assign(({ event }) => {
654
- if (event.type !== "MANUAL_UPLOAD_FILE_SELECTED") return {};
655
- const updated = {
656
- errorMessage: null,
657
- errorSide: null
658
- };
659
- if (event.side === "front") updated.frontFile = event.file;
660
- else if (event.side === "back") updated.backFile = event.file;
661
- else updated.passportFile = event.file;
662
- return updated;
663
- }),
664
- markFrontUploaded: assign(() => ({
665
- frontUploaded: true,
666
- errorMessage: null,
667
- errorSide: null
668
- })),
669
- markBackUploaded: assign(() => ({
670
- backUploaded: true,
671
- errorMessage: null,
672
- errorSide: null
673
- })),
674
- markPassportUploaded: assign(() => ({
675
- passportUploaded: true,
676
- errorMessage: null,
677
- errorSide: null
678
- })),
679
- recordSkipBackFromResponse: assign(({ event }) => {
680
- const response = getUploadResponseFromEvent(event);
681
- if (!response) return {};
682
- if (!response.skipBackIdCapture) return {};
683
- return { skipBackFromServer: true };
684
- }),
685
- decrementManualUploadRetries: assign(({ context }) => ({ retriesLeft: Math.max(0, context.retriesLeft - 1) })),
686
- setManualUploadErrorFromResponse: assign(({ context, event }) => {
687
- const response = getUploadResponseFromEvent(event);
688
- if (!response) return {};
689
- return { errorMessage: mapFailReasonToErrorKey(response, context.activeTab) ?? "manualIdUpload.generic" };
690
- }),
691
- setManualUploadGenericError: assign(() => ({ errorMessage: "manualIdUpload.generic" })),
692
- setManualUploadFileTooLargeError: assign(({ event }) => {
693
- return {
694
- errorMessage: "manualIdUpload.fileTooBig",
695
- errorSide: event.type === "MANUAL_UPLOAD_FILE_SELECTED" ? event.side : null
696
- };
697
- }),
698
- markErrorSideFront: assign(() => ({ errorSide: "front" })),
699
- markErrorSideBack: assign(() => ({ errorSide: "back" })),
700
- markErrorSidePassport: assign(() => ({ errorSide: "passport" })),
701
- clearManualUploadError: assign(() => ({
702
- errorMessage: null,
703
- errorSide: null
704
- }))
705
- },
706
- guards: {
707
- isAllowedTabChange: ({ context, event }) => event.type === "MANUAL_UPLOAD_TAB_CHANGED" && (!context.onlyBack || event.tab === "id"),
708
- isSelectedFileTooLarge: ({ context, event }) => {
709
- if (event.type !== "MANUAL_UPLOAD_FILE_SELECTED") return false;
710
- if (context.onlyBack && event.side !== "back") return false;
711
- return event.file.size > MANUAL_UPLOAD_MAX_FILE_SIZE_BYTES;
712
- },
713
- isFrontFileSelection: ({ context, event }) => !context.onlyBack && event.type === "MANUAL_UPLOAD_FILE_SELECTED" && event.side === "front",
714
- isBackFileSelection: ({ event }) => event.type === "MANUAL_UPLOAD_FILE_SELECTED" && event.side === "back",
715
- isPassportFileSelection: ({ context, event }) => !context.onlyBack && event.type === "MANUAL_UPLOAD_FILE_SELECTED" && event.side === "passport",
716
- manualUploadRetriesExhausted: ({ context }) => context.retriesLeft <= 0,
717
- canContinueFromIdTab: ({ context }) => {
718
- if (context.activeTab !== "id") return false;
719
- if (context.onlyBack) return context.backUploaded;
720
- if (!context.frontUploaded) return false;
721
- if (context.onlyFront) return true;
722
- if (context.skipBackFromServer) return true;
723
- return context.backUploaded;
724
- },
725
- canContinueFromPassportTab: ({ context }) => !context.onlyBack && context.activeTab === "passport" && context.passportUploaded,
726
- manualUploadResponseHasError: ({ context, event }) => {
727
- const response = getUploadResponseFromEvent(event);
728
- if (!response) return false;
729
- return mapFailReasonToErrorKey(response, context.activeTab) !== null;
730
- },
731
- manualUploadPayloadTooLarge: ({ event }) => getUploadOutputFromEvent(event)?.type === "fileTooLarge"
732
- }
733
- }).createMachine({
734
- id: "manualUpload",
735
- initial: "selecting",
736
- context: ({ input }) => ({
737
- onlyFront: input.onlyFront,
738
- onlyBack: input.onlyBack ?? false,
739
- isSecondId: input.isSecondId,
740
- captureAttempts: input.captureAttempts,
741
- extractIdFace: input.extractIdFace,
742
- frontFile: null,
743
- backFile: null,
744
- passportFile: null,
745
- activeTab: "id",
746
- retriesLeft: input.captureAttempts,
747
- errorMessage: null,
748
- errorSide: null,
749
- skipBackFromServer: false,
750
- frontUploaded: false,
751
- backUploaded: false,
752
- passportUploaded: false
753
- }),
754
- on: {
755
- MANUAL_UPLOAD_TAB_CHANGED: {
756
- guard: "isAllowedTabChange",
757
- target: ".selecting",
758
- actions: ["setManualUploadActiveTab"]
759
- },
760
- MANUAL_UPLOAD_RESET: {
761
- target: ".selecting",
762
- actions: ["resetManualUpload"]
763
- }
764
- },
765
- states: {
766
- selecting: { on: {
767
- MANUAL_UPLOAD_FILE_SELECTED: [
768
- {
769
- guard: "isSelectedFileTooLarge",
770
- actions: ["setManualUploadFileTooLargeError"]
771
- },
772
- {
773
- guard: "isFrontFileSelection",
774
- target: "uploadingFront",
775
- actions: ["storeSelectedFile"]
776
- },
777
- {
778
- guard: "isBackFileSelection",
779
- target: "uploadingBack",
780
- actions: ["storeSelectedFile"]
781
- },
782
- {
783
- guard: "isPassportFileSelection",
784
- target: "uploadingPassport",
785
- actions: ["storeSelectedFile"]
786
- }
787
- ],
788
- MANUAL_UPLOAD_CONTINUE: [{
789
- guard: "canContinueFromIdTab",
790
- target: "finished"
791
- }, {
792
- guard: "canContinueFromPassportTab",
793
- target: "finished"
794
- }]
795
- } },
796
- uploadingFront: { invoke: {
797
- id: "uploadManualFile",
798
- src: "uploadManualFile",
799
- input: ({ context }) => ({
800
- side: "front",
801
- file: context.frontFile,
802
- retry: context.retriesLeft < context.captureAttempts,
803
- onlyFront: context.onlyFront,
804
- isSecondId: context.isSecondId,
805
- extractIdFace: context.extractIdFace
806
- }),
807
- onDone: [
808
- {
809
- guard: "manualUploadPayloadTooLarge",
810
- target: "selecting",
811
- actions: ["setManualUploadFileTooLargeError", "markErrorSideFront"]
812
- },
813
- {
814
- guard: "manualUploadResponseHasError",
815
- target: "checkRetries",
816
- actions: [
817
- "decrementManualUploadRetries",
818
- "setManualUploadErrorFromResponse",
819
- "markErrorSideFront"
820
- ]
821
- },
822
- {
823
- target: "selecting",
824
- actions: [
825
- "markFrontUploaded",
826
- "recordSkipBackFromResponse",
827
- "clearManualUploadError"
828
- ]
829
- }
830
- ],
831
- onError: {
832
- target: "checkRetries",
833
- actions: [
834
- "decrementManualUploadRetries",
835
- "setManualUploadGenericError",
836
- "markErrorSideFront"
837
- ]
838
- }
839
- } },
840
- uploadingBack: { invoke: {
841
- id: "uploadManualFile",
842
- src: "uploadManualFile",
843
- input: ({ context }) => ({
844
- side: "back",
845
- file: context.backFile,
846
- retry: context.retriesLeft < context.captureAttempts,
847
- onlyFront: false,
848
- isSecondId: context.isSecondId
849
- }),
850
- onDone: [
851
- {
852
- guard: "manualUploadPayloadTooLarge",
853
- target: "selecting",
854
- actions: ["setManualUploadFileTooLargeError", "markErrorSideBack"]
855
- },
856
- {
857
- guard: "manualUploadResponseHasError",
858
- target: "checkRetries",
859
- actions: [
860
- "decrementManualUploadRetries",
861
- "setManualUploadErrorFromResponse",
862
- "markErrorSideBack"
863
- ]
864
- },
865
- {
866
- target: "selecting",
867
- actions: ["markBackUploaded", "clearManualUploadError"]
868
- }
869
- ],
870
- onError: {
871
- target: "checkRetries",
872
- actions: [
873
- "decrementManualUploadRetries",
874
- "setManualUploadGenericError",
875
- "markErrorSideBack"
876
- ]
877
- }
878
- } },
879
- uploadingPassport: { invoke: {
880
- id: "uploadManualFile",
881
- src: "uploadManualFile",
882
- input: ({ context }) => ({
883
- side: "passport",
884
- file: context.passportFile,
885
- retry: context.retriesLeft < context.captureAttempts,
886
- onlyFront: false,
887
- isSecondId: context.isSecondId,
888
- extractIdFace: context.extractIdFace
889
- }),
890
- onDone: [
891
- {
892
- guard: "manualUploadPayloadTooLarge",
893
- target: "selecting",
894
- actions: ["setManualUploadFileTooLargeError", "markErrorSidePassport"]
895
- },
896
- {
897
- guard: "manualUploadResponseHasError",
898
- target: "checkRetries",
899
- actions: [
900
- "decrementManualUploadRetries",
901
- "setManualUploadErrorFromResponse",
902
- "markErrorSidePassport"
903
- ]
904
- },
905
- {
906
- target: "selecting",
907
- actions: ["markPassportUploaded", "clearManualUploadError"]
908
- }
909
- ],
910
- onError: {
911
- target: "checkRetries",
912
- actions: [
913
- "decrementManualUploadRetries",
914
- "setManualUploadGenericError",
915
- "markErrorSidePassport"
916
- ]
917
- }
918
- } },
919
- checkRetries: { always: [{
920
- guard: "manualUploadRetriesExhausted",
921
- target: "retriesExhausted"
922
- }, { target: "selecting" }] },
923
- retriesExhausted: { after: { [MANUAL_UPLOAD_EXHAUSTION_FORWARD_MS]: { target: "exhausted" } } },
924
- exhausted: {
925
- type: "final",
926
- output: { result: "exhausted" }
644
+ }
645
+ function createApi({ actor, trackElementClicked }) {
646
+ return {
647
+ /** Restarts the handshake after a failure. Needs a fresh user gesture. */
648
+ retry() {
649
+ trackElementClicked?.("retry");
650
+ actor.send({ type: "RETRY" });
927
651
  },
928
- finished: {
929
- type: "final",
930
- output: { result: "success" }
652
+ /**
653
+ * Abandons the wallet path from a failure and moves the manager to
654
+ * `closed`, so the consumer can fall back to a camera ID capture.
655
+ */
656
+ useAnotherMethod() {
657
+ trackElementClicked?.("useAnotherMethod");
658
+ actor.send({ type: "USE_ANOTHER_METHOD" });
931
659
  }
932
- }
933
- });
660
+ };
661
+ }
662
+ /**
663
+ * Creates a headless device-wallet (mDL) manager — the V2 equivalent of V1's
664
+ * `renderWallet`, with the intermediate states observable.
665
+ *
666
+ * Runs the Apple Wallet (web) / Google Wallet handshake with zero Incode UI,
667
+ * so integrators trigger it from their own button and render their own UX.
668
+ * Call it from a user gesture: iOS Safari requires transient user activation
669
+ * for `navigator.credentials.get()`.
670
+ *
671
+ * Requires an initialized session (`setup({ apiURL, token })`) — the request
672
+ * and decrypt calls are session-scoped.
673
+ *
674
+ * @example
675
+ * ```typescript
676
+ * import { setup } from '@incodetech/core';
677
+ * import { createDeviceWalletManager } from '@incodetech/core/id';
678
+ *
679
+ * setup({ apiURL, token });
680
+ *
681
+ * button.addEventListener('click', () => {
682
+ * const manager = createDeviceWalletManager();
683
+ * manager.subscribe((state) => {
684
+ * if (state.status === 'finished') manager.stop();
685
+ * if (state.status === 'failed') console.error(state.reason);
686
+ * });
687
+ * });
688
+ * ```
689
+ */
690
+ function createDeviceWalletManager(options = {}) {
691
+ return createDeviceWalletManagerFromActor(createDeviceWalletActor(options));
692
+ }
693
+ /**
694
+ * Creates a device-wallet manager from a pre-built actor. Use this when
695
+ * overriding the machine via `.provide()` for custom backends or tests.
696
+ */
697
+ function createDeviceWalletManagerFromActor(actor) {
698
+ return createManager({
699
+ actor,
700
+ mapState,
701
+ createApi,
702
+ instrumentation: createManagerInstrumentation(eventModuleNames.idCapture, { getEventScreenName: getDeviceWalletScreenName })
703
+ });
704
+ }
705
+ //#endregion
706
+ //#region src/modules/id/deviceWallet/renderWallet.ts
707
+ const FAIL_MESSAGES = {
708
+ UNSUPPORTED_PLATFORM: "Device wallet is not supported on this platform/browser",
709
+ NO_CREDENTIAL: "The wallet returned no digital ID credential",
710
+ WALLET_ERROR: "Device wallet request failed"
711
+ };
712
+ /**
713
+ * Headless device-wallet (mDL) API — the V1 `renderWallet` shape, ported.
714
+ *
715
+ * Runs the Apple Wallet (web) / Google Wallet handshake with zero Incode UI,
716
+ * so integrators trigger it from their own button and render their own UX.
717
+ * Resolves once the handshake settles; the outcome arrives through
718
+ * `onSuccess` / `onError` exactly as in V1.
719
+ *
720
+ * MUST be called from a user gesture (e.g. a click handler) — iOS/Safari
721
+ * requires transient user activation for `navigator.credentials.get()` to
722
+ * present the wallet sheet.
723
+ *
724
+ * Requires an initialized session (`setup({ apiURL, token })`). The verified
725
+ * attributes are NOT returned here: they land in the session and are read back
726
+ * through the standard OCR / score APIs. `onSuccess` receives the submission
727
+ * decision/status.
728
+ *
729
+ * Prefer {@link createDeviceWalletManager} for new integrations — it exposes
730
+ * the intermediate `requesting` / `presenting` / `decrypting` states (and a
731
+ * `retry()`), which this one-shot wrapper collapses.
732
+ *
733
+ * @example
734
+ * ```typescript
735
+ * button.addEventListener('click', () => {
736
+ * void renderWallet({
737
+ * onSuccess: (result) => console.log(result.decision),
738
+ * onError: (error) => console.error(error.code, error.message),
739
+ * });
740
+ * });
741
+ * ```
742
+ */
743
+ function renderWallet({ onSuccess, onError, ...actorOptions }) {
744
+ return new Promise((resolve) => {
745
+ const manager = createDeviceWalletManager(actorOptions);
746
+ const subscription = {};
747
+ let settled = false;
748
+ const settle = (state) => {
749
+ if (state.status === "requesting" || state.status === "presenting" || state.status === "decrypting") return false;
750
+ settled = true;
751
+ subscription.unsubscribe?.();
752
+ manager.stop();
753
+ if (state.status === "finished") onSuccess(state.response);
754
+ else if (state.status === "failed") onError({
755
+ code: state.reason,
756
+ message: FAIL_MESSAGES[state.reason]
757
+ });
758
+ else onError({
759
+ code: "WALLET_ERROR",
760
+ message: FAIL_MESSAGES.WALLET_ERROR
761
+ });
762
+ resolve();
763
+ return true;
764
+ };
765
+ if (settle(manager.getState())) return;
766
+ subscription.unsubscribe = manager.subscribe((state) => {
767
+ if (settled) return;
768
+ settle(state);
769
+ });
770
+ });
771
+ }
934
772
  //#endregion
935
773
  //#region src/modules/id/idCaptureGuards.ts
936
774
  function getPermissionOutput(event) {
@@ -941,7 +779,7 @@ function getPermissionOutput(event) {
941
779
  }
942
780
  const hasShowTutorialGuard = ({ context }) => context.config.showTutorial;
943
781
  const hasAgeAssuranceGuard = ({ context }) => context.config.ageAssurance === true;
944
- const hasShowDocumentChooserGuard = ({ context }) => (!context.config.onlyBack || context.config.manualUploadIdCapture === true) && (context.config.showDocumentChooserScreen ?? false);
782
+ const hasShowDocumentChooserGuard = ({ context }) => !context.config.onlyBack && (context.config.showDocumentChooserScreen ?? false);
945
783
  const isPermissionGrantedGuard = ({ event }) => {
946
784
  return getPermissionOutput(event) === "granted";
947
785
  };
@@ -959,6 +797,7 @@ const isPermissionDeniedErrorGuard = ({ event }) => {
959
797
  return false;
960
798
  };
961
799
  const isPermissionBlockedGuard = ({ context }) => context.permissionResult === "denied" || context.permissionResult === "refresh";
800
+ const requiresMotionPermissionUserGestureGuard = ({ context }) => requiresMotionPermissionUserGesture(context.config.ds);
962
801
  const hasAttemptsRemainingGuard = ({ context }) => context.attemptsRemaining > 0;
963
802
  const hasCapturedImageGuard = ({ context }) => {
964
803
  return context.provider?.getCapturedCanvas() !== null;
@@ -994,11 +833,9 @@ const isFrontModeGuard = ({ context }) => context.currentMode === "front" || con
994
833
  * server answer exists, and
995
834
  * 2. where you do, still self-correct once the answer arrives.
996
835
  *
997
- * Two variants qualify under (1): the captureOnly machine
998
- * (`idCaptureOnlyStateMachine` — no upload, so no answer ever exists) and the
999
- * manual-upload sub-machine (slot visibility precedes any upload, and it ORs
1000
- * `skipBackFromServer` afterwards to satisfy (2)). This guard is on neither
1001
- * path — the answer is already in hand by the time it runs.
836
+ * The captureOnly machine qualifies under (1) because it has no upload and no
837
+ * server answer. This guard is not on that path, so the answer is already in
838
+ * hand by the time it runs.
1002
839
  */
1003
840
  const shouldContinueToBackGuard = ({ context }) => {
1004
841
  if (context.currentMode !== "front" && context.currentMode !== "passport") return false;
@@ -1038,10 +875,8 @@ const supportsOptimizingStatusGuard = ({ context }) => context.config.supportsOp
1038
875
  const isDeepsightEnabledGuard = ({ context }) => context.dependencies !== void 0;
1039
876
  const isDeepsightReadyGuard = ({ context }) => context.deepsightService !== void 0;
1040
877
  const needsDeepsightInitGuard = ({ context }) => context.dependencies !== void 0 && context.deepsightService === void 0 && !context.deepsightInitAttempted;
1041
- const hasOnlyManualUploadAvailableGuard = ({ context }) => context.availableDocumentTypes.length === 1 && context.availableDocumentTypes[0] === "manualIdUpload";
1042
878
  /** No capture option is enabled — the ID module is misconfigured. */
1043
879
  const hasNoDocumentTypesAvailableGuard = ({ context }) => context.availableDocumentTypes.length === 0;
1044
- const isManualIdUploadSelectedGuard = ({ event }) => event.type === "SELECT_DOCUMENT" && event.documentType === "manualIdUpload";
1045
880
  const isDigitalIdUploadSelectedGuard = ({ event }) => event.type === "SELECT_DOCUMENT" && event.documentType === "digitalIdUpload";
1046
881
  const isDigitalIdRedirectSelectedGuard = ({ event }) => event.type === "SELECT_DOCUMENT" && event.documentType === "digitalIdRedirect";
1047
882
  /** The Digital ID (device wallet) chooser tile was picked. */
@@ -1110,239 +945,27 @@ const forceFinishAfterProcessingGuard = ({ context }) => context.forceFinishAfte
1110
945
  * the dashboard-configurable `config.captureAttempts`, unlike the per-upload
1111
946
  * (camera) same-side expired path, which uses the fixed
1112
947
  * `MAX_EXPIRED_FIRST_SIDE_ATTEMPTS` (decoupled for V1 parity — see ENG-46592).
1113
- * The two expired caps are therefore different by design.
1114
- */
1115
- const isProcessingExpiredExhaustedGuard = ({ context }) => context.processingExpiredAttempts + 1 >= context.config.captureAttempts;
1116
- /**
1117
- * True when capturing the back side of a two-sided (front+back) ID flow.
1118
- * Used to redirect an expired-back capture to front rather than retrying
1119
- * the same back side (Figma Story #2.1).
1120
- * Excludes onlyBack and usSmartCapture — those are single-side back flows
1121
- * with no front to redirect to.
1122
- */
1123
- const isBackModeInMultiSideFlowGuard = ({ context }) => context.currentMode === "back" && !context.config.onlyBack && !context.config.usSmartCapture;
1124
- /**
1125
- * True once the user has already been redirected from an expired second-side
1126
- * (back) capture to the first side (front) for their one extra attempt
1127
- * (V1's `expiredRedirected`). Used to make the expired-back→front redirect a
1128
- * one-shot: any subsequent expired upload routes to `expiredExhausted` instead
1129
- * of redirecting again, so total captures stay within the backend's
1130
- * max-attempts/OCR cap (ENG-46592 / re-respects ENG-45666).
1131
- */
1132
- const wasExpiredRedirectedGuard = ({ context }) => context.expiredRedirected === true;
1133
- const hasManualCaptureTriggeredGuard = ({ context }) => context.manualCaptureTriggered;
1134
- //#endregion
1135
- //#region src/modules/id/digitalIdRegistry.ts
1136
- /**
1137
- * True when a digital-ID method uses a full-page web-redirect flow (Trinsic
1138
- * iDIN/FTN). Accepts both the module `FlowType` value (`REDIRECT`) and the
1139
- * backend value (`WEB_REDIRECT`). (Lives here rather than in
1140
- * `idCaptureHelpers` to keep the registry free of circular imports; the
1141
- * helpers re-export it.)
1142
- */
1143
- function isRedirectFlowType(flowType) {
1144
- return flowType === "REDIRECT" || flowType === "WEB_REDIRECT";
1145
- }
1146
- const DIGITAL_ID_REGISTRY = {
1147
- mDL: [{
1148
- kind: "incodeWallet",
1149
- documentType: "digitalId",
1150
- configFlag: "deviceWallet"
1151
- }, {
1152
- kind: "trinsicRedirect",
1153
- documentType: "digitalIdRedirect",
1154
- providerId: "trinsic_mdl_placeholder",
1155
- method: "MDL"
1156
- }],
1157
- iDIN: [{
1158
- kind: "trinsicRedirect",
1159
- documentType: "digitalIdRedirect",
1160
- providerId: "trinsic_netherlands_idin",
1161
- method: "IDIN"
1162
- }],
1163
- FTN: [{
1164
- kind: "trinsicRedirect",
1165
- documentType: "digitalIdRedirect",
1166
- providerId: "trinsic_finnish_trust_network",
1167
- method: "FTN"
1168
- }]
1169
- };
1170
- const DIGITAL_ID_DOCUMENT_TYPES = Object.keys(DIGITAL_ID_REGISTRY);
1171
- /**
1172
- * Tolerant view over `config.digitalIds`: accepts both the mapped array
1173
- * (`IdCaptureDigitalIdMethod[]`) and the raw backend object
1174
- * (`{ enabled, methods: [...] }`). Extracted from the old
1175
- * `getDigitalIdRedirectMethod` cast so every consumer parses it the same way.
1176
- */
1177
- function normalizeDigitalIdMethods(config) {
1178
- const raw = config.digitalIds;
1179
- return Array.isArray(raw) ? raw : raw?.methods ?? [];
1180
- }
1181
- /** Whether a funnel entry is activated by config + platform capabilities. */
1182
- function isEntryActive(entry, config, platform) {
1183
- if (entry.kind === "incodeWallet") return config[entry.configFlag] === true && platform.credentialsAvailable;
1184
- return findConfiguredMethod(entry, config) !== void 0;
1185
- }
1186
- /**
1187
- * The configured `digitalIds` method matching a Trinsic funnel entry — by
1188
- * persisted method name, or (mapped shape) by provider id on a redirect
1189
- * flowType. Mirrors the old first-match semantics; `methods[].enabled` is
1190
- * deliberately still not consulted (pre-existing behavior).
1191
- */
1192
- function findConfiguredMethod(entry, config) {
1193
- return normalizeDigitalIdMethods(config).find((method) => method.method?.toUpperCase() === entry.method || method.provider === entry.providerId && !!method.flowType && isRedirectFlowType(method.flowType));
1194
- }
1195
- /** Normalizes a raw configured method into the mapped shape for the child input. */
1196
- function toMappedMethod(entry, method) {
1197
- if (method.flowType && isRedirectFlowType(method.flowType)) return method;
1198
- return {
1199
- method: method.method,
1200
- provider: entry.providerId,
1201
- flowType: "WEB_REDIRECT",
1202
- country: method.country ?? method.countries?.[0],
1203
- displayName: method.displayName
1204
- };
1205
- }
1206
- /**
1207
- * Resolves the implementation that should fulfil a digital-ID document type:
1208
- * the first funnel entry that is active under the given config and platform.
1209
- * `undefined` when nothing can run it (the chooser then shows no tile for it).
1210
- */
1211
- function resolveDigitalIdImplementation(docType, config, platform) {
1212
- for (const entry of DIGITAL_ID_REGISTRY[docType]) {
1213
- if (!isEntryActive(entry, config, platform)) continue;
1214
- if (entry.kind === "incodeWallet") return { kind: "incodeWallet" };
1215
- const method = findConfiguredMethod(entry, config);
1216
- return {
1217
- kind: "trinsicRedirect",
1218
- providerId: entry.providerId,
1219
- method: method ? toMappedMethod(entry, method) : void 0
1220
- };
1221
- }
1222
- }
1223
- //#endregion
1224
- //#region src/modules/id/idCaptureHelpers.ts
1225
- /**
1226
- * Persisted method name → catalog provider id, derived from the digital-ID
1227
- * registry's Trinsic entries (single source of truth — was a hardcoded map
1228
- * here before the registry existed).
1229
- */
1230
- const REDIRECT_PROVIDER_BY_METHOD = (() => {
1231
- const map = {};
1232
- for (const docType of DIGITAL_ID_DOCUMENT_TYPES) for (const entry of DIGITAL_ID_REGISTRY[docType]) if (entry.kind === "trinsicRedirect") map[entry.method] = entry.providerId;
1233
- return map;
1234
- })();
1235
- /**
1236
- * The first enabled web-redirect digital-ID method (iDIN-only for now), or
1237
- * `undefined` when none is configured. Drives both the `digitalIdRedirect`
1238
- * chooser option and the provider passed to the `digitalIdRedirectMachine`.
1239
- *
1240
- * Accepts both the mapped array (`IdCaptureDigitalIdMethod[]`) and the raw
1241
- * backend `configuration.digitalIds` object (`{ enabled, methods: [...] }`);
1242
- * for the raw shape it derives `provider`/`flowType` from the persisted method
1243
- * name via the digital-ID registry (`IDIN` → `trinsic_netherlands_idin`,
1244
- * `WEB_REDIRECT`).
1245
- */
1246
- function getDigitalIdRedirectMethod(config) {
1247
- for (const method of normalizeDigitalIdMethods(config)) {
1248
- if (method.flowType && isRedirectFlowType(method.flowType)) return method;
1249
- const provider = method.method ? REDIRECT_PROVIDER_BY_METHOD[method.method.toUpperCase()] : void 0;
1250
- if (provider) return {
1251
- method: method.method,
1252
- provider,
1253
- flowType: "WEB_REDIRECT",
1254
- country: method.country ?? method.countries?.[0],
1255
- displayName: method.displayName
1256
- };
1257
- }
1258
- }
1259
- /**
1260
- * Map of config properties received from the backend to adjusted, internal document types.
1261
- */
1262
- const DOCUMENT_TYPES_CONFIG_TO_ADJUSTED_MAP = [
1263
- ["enableId", "id"],
1264
- ["enablePassport", "passport"],
1265
- ["manualUploadIdCapture", "manualIdUpload"],
1266
- ["digitalIdsUpload", "digitalIdUpload"]
1267
- ];
1268
- /**
1269
- * Returns the list of document types enabled by config for the chooser screen, in display order.
1270
- * Used to populate chooser state so UI and headless consumers can render the correct options.
1271
- *
1272
- * Digital-ID document types (device wallet, Trinsic web-redirect) come from
1273
- * the digital-ID registry funnel: each registry document type contributes the
1274
- * tile of its first active implementation. A `deviceWallet: true` config on a
1275
- * platform without the Credentials API therefore shows the Trinsic fallback
1276
- * tile when one is configured — or no tile at all, instead of a dead-end one.
1277
- *
1278
- * @param config - ID capture configuration (from flow or init)
1279
- * @param platform - Platform capabilities consulted by the digital-ID funnel.
1280
- * Defaults to no Credentials API for pure-node callers; the machine passes
1281
- * the real capability check.
1282
- * @returns Array of IdDocumentType values that should be shown in the document type chooser
948
+ * The two expired caps are therefore different by design.
1283
949
  */
1284
- function getAvailableDocumentTypes(config, platform = { credentialsAvailable: false }) {
1285
- if (config.onlyBack && config.manualUploadIdCapture) return config.enableId ? ["id", "manualIdUpload"] : ["manualIdUpload"];
1286
- const adjustedDocumentTypes = [];
1287
- for (const [configProperty, adjustedType] of DOCUMENT_TYPES_CONFIG_TO_ADJUSTED_MAP) if (config[configProperty]) adjustedDocumentTypes.push(adjustedType);
1288
- for (const docType of DIGITAL_ID_DOCUMENT_TYPES) {
1289
- const route = resolveDigitalIdImplementation(docType, config, platform);
1290
- if (!route) continue;
1291
- const documentType = route.kind === "incodeWallet" ? "digitalId" : "digitalIdRedirect";
1292
- if (!adjustedDocumentTypes.includes(documentType)) adjustedDocumentTypes.push(documentType);
1293
- }
1294
- return adjustedDocumentTypes;
1295
- }
950
+ const isProcessingExpiredExhaustedGuard = ({ context }) => context.processingExpiredAttempts + 1 >= context.config.captureAttempts;
1296
951
  /**
1297
- * Which manual-upload tabs to show. Falls back to both tabs when neither
1298
- * `enableId` nor `enablePassport` is set (no accepted documents).
952
+ * True when capturing the back side of a two-sided (front+back) ID flow.
953
+ * Used to redirect an expired-back capture to front rather than retrying
954
+ * the same back side (Figma Story #2.1).
955
+ * Excludes onlyBack and usSmartCapture — those are single-side back flows
956
+ * with no front to redirect to.
1299
957
  */
1300
- function getManualUploadTabs(config) {
1301
- if (config.onlyBack) return {
1302
- showIdTab: true,
1303
- showPassportTab: false
1304
- };
1305
- const showIdTab = !!config.enableId;
1306
- const showPassportTab = !!config.enablePassport;
1307
- if (!showIdTab && !showPassportTab) return {
1308
- showIdTab: true,
1309
- showPassportTab: true
1310
- };
1311
- return {
1312
- showIdTab,
1313
- showPassportTab
1314
- };
1315
- }
958
+ const isBackModeInMultiSideFlowGuard = ({ context }) => context.currentMode === "back" && !context.config.onlyBack && !context.config.usSmartCapture;
1316
959
  /**
1317
- * Computes `idCaptureMachine`'s terminal output — the downstream module-skip
1318
- * signal read by the Flow orchestrator (via `runChildModule`'s `MODULE_COMPLETE`).
1319
- *
1320
- * `skipFaceMatch` is set ONLY for the US Smart Capture *camera back-only* path:
1321
- * a back image was captured on-device (`capturedImages.back`) and no front was
1322
- * (`!capturedImages.front`) — "no front, no ID face, no match".
1323
- *
1324
- * Requiring a captured back is deliberate and load-bearing: the manual-upload
1325
- * and digital-ID-upload paths submit a full ID (front face present) but do NOT
1326
- * populate `capturedImages` (only the camera path does). Gating on a captured
1327
- * back excludes those upload paths so a real ID-vs-selfie match is never
1328
- * silently skipped. `capturedImages.front`/`forceFrontIdCapture` alone are not
1329
- * enough (a later front upload clears the request; uploads set neither field).
1330
- *
1331
- * Always `false` outside US Smart Capture. The selfie/face capture itself is
1332
- * unaffected — only the ID-vs-selfie match is skipped.
1333
- *
1334
- * Also `true` on the Digital ID web-redirect (Trinsic iDIN/FTN) COMPLETED path
1335
- * when the scheme returned no portrait (`isDigitalIdRedirect && !portraitPresent`,
1336
- * FM-1/FM-2): there is no ID face to match the selfie against.
960
+ * True once the user has already been redirected from an expired second-side
961
+ * (back) capture to the first side (front) for their one extra attempt
962
+ * (V1's `expiredRedirected`). Used to make the expired-back→front redirect a
963
+ * one-shot: any subsequent expired upload routes to `expiredExhausted` instead
964
+ * of redirecting again, so total captures stay within the backend's
965
+ * max-attempts/OCR cap (ENG-46592 / re-respects ENG-45666).
1337
966
  */
1338
- function computeIdCaptureOutput(context) {
1339
- const frontIdCaptured = Boolean(context.capturedImages.front);
1340
- const backIdCaptured = Boolean(context.capturedImages.back);
1341
- return {
1342
- frontIdCaptured,
1343
- skipFaceMatch: context.isDigitalIdRedirect === true && context.portraitPresent !== true || context.config.usSmartCapture === true && backIdCaptured && !frontIdCaptured
1344
- };
1345
- }
967
+ const wasExpiredRedirectedGuard = ({ context }) => context.expiredRedirected === true;
968
+ const hasManualCaptureTriggeredGuard = ({ context }) => context.manualCaptureTriggered;
1346
969
  //#endregion
1347
970
  //#region src/modules/id/idDetectionNotificationController.ts
1348
971
  const NOTIFICATION_DURATION_MS = 500;
@@ -1418,9 +1041,26 @@ async function requestIdCapturePermission(input) {
1418
1041
  cameraFallbackLevel: fallbackLevel
1419
1042
  };
1420
1043
  }
1044
+ /**
1045
+ * Resolves and primes Deepsight motion without touching camera permission.
1046
+ * IDC-03 invokes this directly from the tutorial CTA's event macrostep so
1047
+ * iOS keeps the transient user activation required by DeviceMotion.
1048
+ */
1049
+ async function requestIdCaptureMotionPermission() {
1050
+ const result = await requestMotionPermission();
1051
+ return result === "not-required" ? "granted" : result;
1052
+ }
1421
1053
  const requestPermissionActor = fromPromise(async ({ input }) => {
1422
1054
  return requestIdCapturePermission(input);
1423
1055
  });
1056
+ /**
1057
+ * Takes no `AbortSignal` on purpose: the work it awaits is the OS-owned
1058
+ * `DeviceMotionEvent.requestPermission()` prompt, which exposes no
1059
+ * cancellation. XState still discards the settled result if the state is
1060
+ * left, and `primeWasmMotionSensor` is idempotent, so an abandoned request
1061
+ * cannot mis-drive the machine.
1062
+ */
1063
+ const requestMotionPermissionActor = fromPromise(() => requestIdCaptureMotionPermission());
1424
1064
  fromPromise(async ({ input }) => {
1425
1065
  return initializeIdCapture(input);
1426
1066
  });
@@ -1781,7 +1421,7 @@ fromPromise(async ({ input }) => {
1781
1421
  });
1782
1422
  fromPromise(async ({ input }) => {
1783
1423
  if (!input.dependencies) return;
1784
- const { loadDeepsightSession } = await import("./deepsightLoader-v-dLejS-.esm.js");
1424
+ const { loadDeepsightSession } = await import("./deepsightLoader-DvfAYnzg.esm.js");
1785
1425
  return loadDeepsightSession({
1786
1426
  ds: input.ds,
1787
1427
  storage: input.dependencies.storage,
@@ -2224,7 +1864,7 @@ function cleanup(service) {
2224
1864
  service.cleanup();
2225
1865
  } catch {}
2226
1866
  }
2227
- function createIdMetadataPreparationCoordinator(load = async (input) => (await import("./deepsightLoader-v-dLejS-.esm.js")).loadDeepsightSession(input)) {
1867
+ function createIdMetadataPreparationCoordinator(load = async (input) => (await import("./deepsightLoader-DvfAYnzg.esm.js")).loadDeepsightSession(input)) {
2228
1868
  let disposed = false;
2229
1869
  let preparation;
2230
1870
  let service;
@@ -2257,6 +1897,46 @@ function createIdMetadataPreparationCoordinator(load = async (input) => (await i
2257
1897
  };
2258
1898
  }
2259
1899
  //#endregion
1900
+ //#region src/modules/id/idCaptureOnlyConfig.ts
1901
+ /**
1902
+ * The `IdCaptureConfig` keys the captureOnly flow supports.
1903
+ */
1904
+ const ID_CAPTURE_ONLY_CONFIG_KEYS = [
1905
+ "showTutorial",
1906
+ "captureAttempts",
1907
+ "autoCaptureTimeout",
1908
+ "deviceIdleTimeout",
1909
+ "enableId",
1910
+ "enablePassport",
1911
+ "onlyFront",
1912
+ "onlyBack",
1913
+ "showDocumentChooserScreen"
1914
+ ];
1915
+ /**
1916
+ * `onCapture` marks a config as captureOnly. It is not part of
1917
+ * `IdCaptureConfig`, but JavaScript callers (and `<incode-id>`'s `config`
1918
+ * property) can still pass it.
1919
+ */
1920
+ function hasOnCaptureCallback(config) {
1921
+ return typeof config.onCapture === "function";
1922
+ }
1923
+ /** Keeps the supported keys and `onCapture`, dropping backend-only options. */
1924
+ function pickIdCaptureOnlyConfig(config) {
1925
+ return {
1926
+ ...Object.fromEntries(ID_CAPTURE_ONLY_CONFIG_KEYS.filter((key) => config[key] !== void 0).map((key) => [key, config[key]])),
1927
+ onCapture: config.onCapture
1928
+ };
1929
+ }
1930
+ /**
1931
+ * Whether the captureOnly flow needs the back side after this capture. There
1932
+ * is no backend answer (`skipBackIdCapture`), so `onlyFront` decides. Drives
1933
+ * both the `shouldContinueToBack` guard and the manager's `needsBackCapture`.
1934
+ */
1935
+ function captureOnlyNeedsBack({ currentMode, config }) {
1936
+ if (currentMode !== "front" && currentMode !== "passport") return false;
1937
+ return !config.onlyFront && !config.onlyBack && !config.usSmartCapture;
1938
+ }
1939
+ //#endregion
2260
1940
  //#region src/modules/id/idProtectedCapture.ts
2261
1941
  const ID_PROTECTED_CAPTURE_CONTROLLER = Symbol("idProtectedCaptureController");
2262
1942
  function attachIdProtectedCaptureController(owner, controller) {
@@ -2294,12 +1974,12 @@ function streamUsesBackCamera(stream) {
2294
1974
  const track = stream.getVideoTracks()[0];
2295
1975
  if (!track) return;
2296
1976
  const settings = typeof track.getSettings === "function" ? track.getSettings() : {};
2297
- const constrainedFacingMode = typeof track.getConstraints === "function" ? track.getConstraints().facingMode : void 0;
1977
+ const constrainedFacingMode = getDeviceClass() !== "desktop" && typeof track.getConstraints === "function" ? track.getConstraints().facingMode : void 0;
2298
1978
  const facingMode = settings.facingMode ?? (typeof constrainedFacingMode === "string" ? constrainedFacingMode : void 0);
2299
1979
  if (facingMode === "environment") return true;
2300
1980
  if (facingMode === "user") return false;
2301
1981
  const label = typeof track.label === "string" ? track.label.toLowerCase() : "";
2302
- if (label.includes("back") || label.includes("rear")) return true;
1982
+ if (isBackCameraLabel(label)) return true;
2303
1983
  if (label.includes("front") || label.includes("user")) return false;
2304
1984
  }
2305
1985
  function createIdProtectedCaptureController(dependencies = {}) {
@@ -2896,6 +2576,8 @@ function createProtectedIdCallbackSession(input) {
2896
2576
  let countdownTimers = [];
2897
2577
  let qualityElements;
2898
2578
  let isCapturing = false;
2579
+ let active = true;
2580
+ let paused = false;
2899
2581
  let lastReportedSide = null;
2900
2582
  let blurSignals = 0;
2901
2583
  let bestFrameSignals = 0;
@@ -2922,7 +2604,7 @@ function createProtectedIdCallbackSession(input) {
2922
2604
  };
2923
2605
  const startCountdown = () => {
2924
2606
  clearCountdown();
2925
- const stepMs = (input.context.config.thresholds?.framesAggregationInterval ?? DEFAULT_ID_CAPTURE_THRESHOLDS.framesAggregationInterval) / 3;
2607
+ const stepMs = resolveCountdownStepMs(input.context.config);
2926
2608
  input.send({
2927
2609
  type: "COUNTER_VALUE_CHANGE",
2928
2610
  value: 3
@@ -2946,114 +2628,139 @@ function createProtectedIdCallbackSession(input) {
2946
2628
  send: setDetectionStatus,
2947
2629
  isSuppressed: () => isReportedIdSideWrong(input.context.currentMode, lastReportedSide)
2948
2630
  });
2949
- return {
2950
- callbacks: {
2951
- onFarAway: () => {
2952
- if (!isCapturing) notifications.show("farAway");
2953
- },
2954
- onDetectionStarted: () => {},
2955
- onMaskChange: (_show, _mask, _top, orientation) => {
2956
- input.send({
2957
- type: "ORIENTATION_CHANGE",
2958
- orientation
2959
- });
2960
- },
2961
- onBlur: () => {
2962
- blurSignals += 1;
2963
- if (!isCapturing) notifications.show("blur");
2964
- },
2965
- onGlare: () => {
2966
- if (!isCapturing) notifications.show("glare");
2967
- },
2968
- onIdNotDetected: () => {
2969
- if (!isCapturing) setDetectionStatus("idNotDetected");
2970
- },
2971
- onSwitchToManualCapture: () => {
2972
- isCapturing = false;
2973
- notifications.clear();
2974
- clearOptimizingBoundary();
2975
- clearCountdown();
2976
- input.send({ type: input.controller.hasCommit() ? "PROTECTED_RELEASE_STASHED_COMMIT" : "SWITCH_TO_MANUAL_CAPTURE" });
2977
- },
2978
- onCapturing: () => {
2979
- const wasCapturing = isCapturing;
2980
- notifications.clear();
2981
- lastReportedSide = null;
2982
- isCapturing = true;
2983
- setDetectionStatus("capturing");
2984
- if (!wasCapturing) logDiagnostic("capture-window", {
2985
- aggregationMs: input.context.config.thresholds?.framesAggregationInterval ?? DEFAULT_ID_CAPTURE_THRESHOLDS.framesAggregationInterval,
2986
- blurSignals
2987
- });
2988
- if (!wasCapturing && input.context.captureFeedbackMode === "countdown") startCountdown();
2989
- if (input.context.captureFeedbackMode === "optimizing" && input.context.config.supportsOptimizingStatus === true && input.context.captureWindowMs != null) {
2990
- clearOptimizingBoundary();
2991
- optimizingBoundary = timer.setTimeout(() => {
2992
- optimizingBoundary = null;
2993
- const presentation = input.controller.getPresentationCanvas();
2994
- if (presentation) {
2995
- presentation.release();
2996
- input.send({ type: "PROTECTED_OPTIMIZING" });
2997
- }
2998
- }, input.context.captureWindowMs);
2999
- }
3000
- },
3001
- onBestFrame: (blur, glare, orientation) => {
3002
- bestFrameSignals += 1;
3003
- bestBlur = bestBlur === void 0 ? blur : Math.min(bestBlur, blur);
3004
- qualityElements = {
3005
- glare,
3006
- sharpness: blur
3007
- };
3008
- if (orientation === "horizontal" || orientation === "vertical") input.send({
3009
- type: "ORIENTATION_CHANGE",
3010
- orientation
3011
- });
3012
- },
3013
- onCapture: (canvas) => {
3014
- isCapturing = false;
2631
+ const callbacks = {
2632
+ onFarAway: () => {
2633
+ if (!active || paused) return;
2634
+ if (!isCapturing) notifications.show("farAway");
2635
+ },
2636
+ onDetectionStarted: () => {},
2637
+ onMaskChange: (_show, _mask, _top, orientation) => {
2638
+ if (!active || paused) return;
2639
+ input.send({
2640
+ type: "ORIENTATION_CHANGE",
2641
+ orientation
2642
+ });
2643
+ },
2644
+ onBlur: () => {
2645
+ if (!active || paused) return;
2646
+ blurSignals += 1;
2647
+ if (!isCapturing) notifications.show("blur");
2648
+ },
2649
+ onGlare: () => {
2650
+ if (!active || paused) return;
2651
+ if (!isCapturing) notifications.show("glare");
2652
+ },
2653
+ onIdNotDetected: () => {
2654
+ if (!active || paused) return;
2655
+ if (!isCapturing) setDetectionStatus("idNotDetected");
2656
+ },
2657
+ onSwitchToManualCapture: () => {
2658
+ if (!active || paused) return;
2659
+ isCapturing = false;
2660
+ notifications.clear();
2661
+ clearOptimizingBoundary();
2662
+ clearCountdown();
2663
+ input.send({ type: input.controller.hasCommit() ? "PROTECTED_RELEASE_STASHED_COMMIT" : "SWITCH_TO_MANUAL_CAPTURE" });
2664
+ },
2665
+ onCapturing: () => {
2666
+ if (!active || paused) return;
2667
+ const wasCapturing = isCapturing;
2668
+ notifications.clear();
2669
+ lastReportedSide = null;
2670
+ isCapturing = true;
2671
+ setDetectionStatus("capturing");
2672
+ if (!wasCapturing) logDiagnostic("capture-window", {
2673
+ aggregationMs: input.context.config.thresholds?.framesAggregationInterval ?? DEFAULT_ID_CAPTURE_THRESHOLDS.framesAggregationInterval,
2674
+ blurSignals
2675
+ });
2676
+ if (!wasCapturing && input.context.captureFeedbackMode === "countdown") startCountdown();
2677
+ if (input.context.captureFeedbackMode === "optimizing" && input.context.config.supportsOptimizingStatus === true && input.context.captureWindowMs != null) {
3015
2678
  clearOptimizingBoundary();
3016
- clearCountdown();
3017
- logDiagnostic("capture-selected", {
3018
- blurSignals,
3019
- bestFrameSignals,
3020
- bestBlur,
3021
- selectedBlur: qualityElements?.sharpness,
3022
- selectedGlare: qualityElements?.glare
3023
- });
3024
- input.controller.storeAutoCaptureFrame(canvas);
3025
- input.send({
3026
- type: "PROTECTED_AUTO_CAPTURE",
3027
- qualityElements
3028
- });
3029
- },
3030
- onIdTypeChange: (idType) => {
3031
- input.send({
3032
- type: "ID_TYPE_CHANGE",
3033
- idType
3034
- });
3035
- },
3036
- onIdSideChange: (side) => {
3037
- if (isCapturing) return;
3038
- lastReportedSide = side;
3039
- if (isReportedIdSideWrong(input.context.currentMode, lastReportedSide)) notifications.clear();
3040
- input.send({
3041
- type: "ID_SIDE_CHANGE",
3042
- side
3043
- });
3044
- },
3045
- onCapturingCounterValueChange: () => {},
3046
- onBarcodeDecoded: (decoded) => {
3047
- input.send({
3048
- type: "BARCODE_DECODED",
3049
- decoded
3050
- });
2679
+ optimizingBoundary = timer.setTimeout(() => {
2680
+ optimizingBoundary = null;
2681
+ const presentation = input.controller.getPresentationCanvas();
2682
+ if (presentation) {
2683
+ presentation.release();
2684
+ input.send({ type: "PROTECTED_OPTIMIZING" });
2685
+ }
2686
+ }, input.context.captureWindowMs);
3051
2687
  }
3052
2688
  },
3053
- dispose: () => {
3054
- notifications.clear();
2689
+ onBestFrame: (blur, glare, orientation) => {
2690
+ if (!active || paused) return;
2691
+ bestFrameSignals += 1;
2692
+ bestBlur = bestBlur === void 0 ? blur : Math.min(bestBlur, blur);
2693
+ qualityElements = {
2694
+ glare,
2695
+ sharpness: blur
2696
+ };
2697
+ if (orientation === "horizontal" || orientation === "vertical") input.send({
2698
+ type: "ORIENTATION_CHANGE",
2699
+ orientation
2700
+ });
2701
+ },
2702
+ onCapture: (canvas) => {
2703
+ if (!active || paused) return;
2704
+ isCapturing = false;
3055
2705
  clearOptimizingBoundary();
3056
2706
  clearCountdown();
2707
+ logDiagnostic("capture-selected", {
2708
+ blurSignals,
2709
+ bestFrameSignals,
2710
+ bestBlur,
2711
+ selectedBlur: qualityElements?.sharpness,
2712
+ selectedGlare: qualityElements?.glare
2713
+ });
2714
+ input.controller.storeAutoCaptureFrame(canvas);
2715
+ input.send({
2716
+ type: "PROTECTED_AUTO_CAPTURE",
2717
+ qualityElements
2718
+ });
2719
+ },
2720
+ onIdTypeChange: (idType) => {
2721
+ if (!active || paused) return;
2722
+ input.send({
2723
+ type: "ID_TYPE_CHANGE",
2724
+ idType
2725
+ });
2726
+ },
2727
+ onIdSideChange: (side) => {
2728
+ if (!active || paused) return;
2729
+ if (isCapturing) return;
2730
+ lastReportedSide = side;
2731
+ if (isReportedIdSideWrong(input.context.currentMode, lastReportedSide)) notifications.clear();
2732
+ input.send({
2733
+ type: "ID_SIDE_CHANGE",
2734
+ side
2735
+ });
2736
+ },
2737
+ onCapturingCounterValueChange: () => {},
2738
+ onBarcodeDecoded: (decoded) => {
2739
+ if (!active || paused) return;
2740
+ input.send({
2741
+ type: "BARCODE_DECODED",
2742
+ decoded
2743
+ });
2744
+ }
2745
+ };
2746
+ const pause = () => {
2747
+ paused = true;
2748
+ notifications.clear();
2749
+ clearOptimizingBoundary();
2750
+ clearCountdown();
2751
+ isCapturing = false;
2752
+ qualityElements = void 0;
2753
+ lastReportedSide = null;
2754
+ };
2755
+ return {
2756
+ callbacks,
2757
+ pause,
2758
+ resume: () => {
2759
+ paused = false;
2760
+ },
2761
+ dispose: () => {
2762
+ active = false;
2763
+ pause();
3057
2764
  }
3058
2765
  };
3059
2766
  }
@@ -3068,7 +2775,7 @@ function createProtectedIdAttemptInput(input) {
3068
2775
  const preAcquired = controller.takePreAcquiredStream();
3069
2776
  let selectedVideoTrack = preAcquired?.stream.getVideoTracks()[0];
3070
2777
  const constraints = getCameraReacquisitionConstraints(selectedVideoTrack, preAcquired?.constraints);
3071
- const captureOnly = "onCapture" in context.config && typeof context.config.onCapture === "function";
2778
+ const captureOnly = hasOnCaptureCallback(context.config);
3072
2779
  const diagnosticAttempt = controller.getDiagnosticAttempt();
3073
2780
  const isAttemptActive = () => !controller.disposed && controller.getProvider() === provider && controller.getDiagnosticAttempt() === diagnosticAttempt;
3074
2781
  const logSelectedCamera = (stream, fallbackLevel, reacquisitionConstraints, preAcquiredStream) => {
@@ -3137,6 +2844,11 @@ function createProtectedIdAttemptInput(input) {
3137
2844
  ...timing
3138
2845
  });
3139
2846
  },
2847
+ pauseDetection: callbackSession.pause,
2848
+ resumeDetection(reset) {
2849
+ if (reset) provider.resetDetectionState();
2850
+ callbackSession.resume();
2851
+ },
3140
2852
  async configureWorkflow() {
3141
2853
  await provider.initialize({});
3142
2854
  configureIdCaptureProvider({
@@ -3199,52 +2911,383 @@ function createProtectedIdAttemptInput(input) {
3199
2911
  }
3200
2912
  };
3201
2913
  }
3202
- function protectedIdResultToCaptureResponse(result) {
3203
- const regulationType = result.regulationType === "US" || result.regulationType === "Worldwide" || result.regulationType === "Other" || result.regulationType === "US_Illinois" || result.regulationType === "US_Texas" || result.regulationType === "US_California" || result.regulationType === "US_Washington" ? result.regulationType : void 0;
2914
+ function protectedIdResultToCaptureResponse(result) {
2915
+ const regulationType = result.regulationType === "US" || result.regulationType === "Worldwide" || result.regulationType === "Other" || result.regulationType === "US_Illinois" || result.regulationType === "US_Texas" || result.regulationType === "US_California" || result.regulationType === "US_Washington" ? result.regulationType : void 0;
2916
+ return {
2917
+ sharpness: result.sharpness ?? 0,
2918
+ correctSharpness: result.sharpness != null && result.sharpness >= 10,
2919
+ glare: result.glare ?? 0,
2920
+ correctGlare: result.glare != null && result.glare >= 10,
2921
+ horizontalResolution: 0,
2922
+ classification: result.classification === true,
2923
+ readability: result.readability === true,
2924
+ typeOfId: result.typeOfId ?? "",
2925
+ issueYear: 0,
2926
+ issueName: "",
2927
+ sessionStatus: result.sessionStatus ?? "",
2928
+ countryCode: result.countryCode ?? "",
2929
+ skipBackIdCapture: result.skipBackIdCapture,
2930
+ skipGlareFront: result.skipGlareFront,
2931
+ skipGlareBack: result.skipGlareBack,
2932
+ acceptedDocuments: result.acceptedDocuments ? [...result.acceptedDocuments] : void 0,
2933
+ forceFrontIdCapture: result.forceFrontIdCapture,
2934
+ idValidationCompleted: result.idValidationCompleted,
2935
+ showMandatoryConsent: result.showMandatoryConsent,
2936
+ regulationType,
2937
+ failReason: result.failReason,
2938
+ side: result.side,
2939
+ idQualityAttemptApproved: result.idQualityAttemptApproved,
2940
+ isDocumentExpired: result.isDocumentExpired,
2941
+ success: result.success
2942
+ };
2943
+ }
2944
+ function getProtectedIdUploadRequest(context, sessionToken = getToken()) {
2945
+ const target = resolveProtectedIdTarget(context.config, context.currentMode);
2946
+ const front = context.currentMode !== "back";
2947
+ const params = { captureType: context.manualCaptureTriggered ? "MANUAL" : "AUTO" };
2948
+ if (front && context.config.onlyFront === true) params.onlyFront = true;
2949
+ if (front && context.config.extractIdFace === false) params.extractIdFace = false;
2950
+ const sharpness = context.qualityElements?.sharpness;
2951
+ if (sharpness !== void 0 && sharpness !== 0 && Number.isFinite(sharpness)) params.sharpness = getRealQualityValue(sharpness);
2952
+ return {
2953
+ target,
2954
+ config: {
2955
+ ...sessionToken ? { headers: { "X-Incode-Hardware-Id": sessionToken } } : {},
2956
+ params
2957
+ }
2958
+ };
2959
+ }
2960
+ //#endregion
2961
+ //#region src/modules/id/digitalIdRegistry.ts
2962
+ /**
2963
+ * True when a digital-ID method uses a full-page web-redirect flow (Trinsic
2964
+ * iDIN/FTN). Accepts both the module `FlowType` value (`REDIRECT`) and the
2965
+ * backend value (`WEB_REDIRECT`). (Lives here rather than in
2966
+ * `idCaptureHelpers` to keep the registry free of circular imports; the
2967
+ * helpers re-export it.)
2968
+ */
2969
+ function isRedirectFlowType(flowType) {
2970
+ return flowType === "REDIRECT" || flowType === "WEB_REDIRECT";
2971
+ }
2972
+ const DIGITAL_ID_REGISTRY = {
2973
+ mDL: [{
2974
+ kind: "incodeWallet",
2975
+ documentType: "digitalId"
2976
+ }, {
2977
+ kind: "trinsicRedirect",
2978
+ documentType: "digitalIdRedirect",
2979
+ providerId: "trinsic_mdl_placeholder",
2980
+ method: "MDL"
2981
+ }],
2982
+ iDIN: [{
2983
+ kind: "trinsicRedirect",
2984
+ documentType: "digitalIdRedirect",
2985
+ providerId: "trinsic_netherlands_idin",
2986
+ method: "IDIN"
2987
+ }],
2988
+ FTN: [{
2989
+ kind: "trinsicRedirect",
2990
+ documentType: "digitalIdRedirect",
2991
+ providerId: "trinsic_finnish_trust_network",
2992
+ method: "FTN"
2993
+ }]
2994
+ };
2995
+ const DIGITAL_ID_DOCUMENT_TYPES = Object.keys(DIGITAL_ID_REGISTRY);
2996
+ /**
2997
+ * Single place that widens `config.digitalIds` to the shapes the backend
2998
+ * actually sends — the mapped array or the raw `{ enabled, methods }` object.
2999
+ * Shared so the cast is written once.
3000
+ */
3001
+ function asDigitalIdsConfig(config) {
3002
+ return config.digitalIds;
3003
+ }
3004
+ /**
3005
+ * Persisted method name → catalog provider id, derived from the registry's
3006
+ * Trinsic entries (single source of truth). Lets the SDK launch iDIN / FTN
3007
+ * from a backend that does not yet send `providerId` on each method.
3008
+ * TODO(roadmap: fully-catalog-driven): remove once every backend enriches
3009
+ * `digitalIds.methods[]` with `providerId` + `flowType`.
3010
+ */
3011
+ const REDIRECT_PROVIDER_BY_METHOD = (() => {
3012
+ const map = {};
3013
+ for (const docType of DIGITAL_ID_DOCUMENT_TYPES) for (const entry of DIGITAL_ID_REGISTRY[docType]) if (entry.kind === "trinsicRedirect") map[entry.method] = entry.providerId;
3014
+ return map;
3015
+ })();
3016
+ /**
3017
+ * Redirect providers that are the fallback leg of a wallet-first funnel
3018
+ * (mDL): they are reached through the `digitalId` tile's funnel resolution,
3019
+ * not offered as a standalone chooser card.
3020
+ */
3021
+ const WALLET_FUNNEL_REDIRECT_PROVIDER_IDS = new Set(DIGITAL_ID_DOCUMENT_TYPES.flatMap((docType) => {
3022
+ const entries = DIGITAL_ID_REGISTRY[docType];
3023
+ if (!entries.some((entry) => entry.kind === "incodeWallet")) return [];
3024
+ return entries.flatMap((entry) => entry.kind === "trinsicRedirect" ? [entry.providerId] : []);
3025
+ }));
3026
+ /**
3027
+ * True for a redirect provider that gets its own chooser card (iDIN, FTN, any
3028
+ * catalog-enriched redirect method) — as opposed to the mDL wallet fallback,
3029
+ * which only surfaces through the Digital ID tile.
3030
+ */
3031
+ function isStandaloneRedirectProvider(providerId) {
3032
+ return !WALLET_FUNNEL_REDIRECT_PROVIDER_IDS.has(providerId);
3033
+ }
3034
+ /**
3035
+ * The provider id a configured method launches with, when it is a
3036
+ * web-redirect method; `undefined` for wallets / other flow shapes.
3037
+ *
3038
+ * Resolution order:
3039
+ * 1. an explicit redirect `flowType` with a `provider` (mapped shape) or
3040
+ * `providerId` (catalog-enriched raw shape) — backend-authoritative;
3041
+ * 2. otherwise the persisted method name via the registry (legacy raw shape).
3042
+ */
3043
+ function resolveRedirectProviderId(method) {
3044
+ if (method.flowType) {
3045
+ if (!isRedirectFlowType(method.flowType)) return;
3046
+ const explicit = method.provider ?? method.providerId;
3047
+ if (explicit) return explicit;
3048
+ }
3049
+ return method.method ? REDIRECT_PROVIDER_BY_METHOD[method.method.toUpperCase()] : void 0;
3050
+ }
3051
+ /**
3052
+ * Tolerant view over `config.digitalIds`: accepts both the mapped array
3053
+ * (`IdCaptureDigitalIdMethod[]`) and the raw backend object
3054
+ * (`{ enabled, methods: [...] }`). Extracted from the old
3055
+ * `getDigitalIdRedirectMethod` cast so every consumer parses it the same way.
3056
+ *
3057
+ * Honors both dashboard toggles: a raw object with `enabled: false` yields no
3058
+ * methods, and individual methods with `enabled: false` are dropped (absent
3059
+ * `enabled` counts as on). This is what "enabled for the flow / workflow"
3060
+ * means — for the redirect chooser cards as much as for the wallet tile.
3061
+ */
3062
+ function normalizeDigitalIdMethods(config) {
3063
+ const raw = asDigitalIdsConfig(config);
3064
+ if (!Array.isArray(raw) && raw?.enabled === false) return [];
3065
+ return (Array.isArray(raw) ? raw : raw?.methods ?? []).filter((method) => method.enabled !== false);
3066
+ }
3067
+ /**
3068
+ * Persisted method names of the Incode-native device wallets — the backend
3069
+ * `DigitalIdMethodCatalog` entries of type `MDL`. A catalog-enriched method
3070
+ * carries `type: 'MDL'` instead of relying on the name; both are honoured.
3071
+ */
3072
+ const DEVICE_WALLET_METHODS = /* @__PURE__ */ new Set([
3073
+ "APPLE_WALLET",
3074
+ "GOOGLE_WALLET",
3075
+ "CA_DMV_WALLET"
3076
+ ]);
3077
+ /**
3078
+ * True for a configured method the native device-wallet handshake can fulfil.
3079
+ * Redirect methods (iDIN, FTN) never count, whatever their name or type — they
3080
+ * must not surface a wallet tile with no wallet behind it.
3081
+ *
3082
+ * Classifies by *kind* only. Whether a method is switched on is already
3083
+ * settled by `normalizeDigitalIdMethods`, which drops disabled methods before
3084
+ * any caller walks the list.
3085
+ */
3086
+ function isDeviceWalletMethod(method) {
3087
+ if (method.flowType && isRedirectFlowType(method.flowType)) return false;
3088
+ if (method.type?.toUpperCase() === "MDL") return true;
3089
+ return method.method !== void 0 && DEVICE_WALLET_METHODS.has(method.method.toUpperCase());
3090
+ }
3091
+ /**
3092
+ * Whether a device wallet is enabled for this ID-capture config: the coarse
3093
+ * `deviceWallet` flag, or an enabled wallet method in `config.digitalIds`.
3094
+ *
3095
+ * The dashboard persists a wallet only in `digitalIds.methods` and leaves the
3096
+ * legacy `deviceWallet` / `appleWallet` booleans at their stored `false`. The
3097
+ * backend derives the booleans from `digitalIds` for the flow response, but
3098
+ * the workflow-node response historically shipped them raw — so a workflow
3099
+ * with Apple Wallet enabled arrived here as `deviceWallet: false`, and the
3100
+ * tile was dropped while SDK 1 (which reads `digitalIds` itself) showed it.
3101
+ * Mirrors the backend's own `deriveDeviceWalletFlag`, minus its "unknown
3102
+ * method counts as a wallet" fallback: an unrecognised method must not
3103
+ * launch a handshake that cannot know what to request.
3104
+ */
3105
+ function isDeviceWalletEnabled(config) {
3106
+ if (config.deviceWallet === true) return true;
3107
+ return hasEnabledDeviceWalletMethod(config);
3108
+ }
3109
+ /** Whether a funnel entry is activated by config + platform capabilities. */
3110
+ function isEntryActive(entry, config, platform) {
3111
+ if (entry.kind === "incodeWallet") return isDeviceWalletEnabled(config) && platform.credentialsAvailable;
3112
+ return findConfiguredMethod(entry, config) !== void 0;
3113
+ }
3114
+ /**
3115
+ * The configured `digitalIds` method matching a Trinsic funnel entry — by
3116
+ * persisted method name, or (mapped shape) by provider id on a redirect
3117
+ * flowType. First match wins; disabled methods are already dropped by
3118
+ * `normalizeDigitalIdMethods`.
3119
+ */
3120
+ function findConfiguredMethod(entry, config) {
3121
+ return normalizeDigitalIdMethods(config).find((method) => method.method?.toUpperCase() === entry.method || method.provider === entry.providerId && !!method.flowType && isRedirectFlowType(method.flowType));
3122
+ }
3123
+ /** Normalizes a raw configured method into the mapped shape for the child input. */
3124
+ function toMappedMethod(entry, method) {
3125
+ if (method.flowType && isRedirectFlowType(method.flowType)) return method;
3126
+ return {
3127
+ method: method.method,
3128
+ provider: entry.providerId,
3129
+ flowType: "WEB_REDIRECT",
3130
+ country: method.country ?? method.countries?.[0],
3131
+ displayName: method.displayName,
3132
+ logoUrl: method.logoUrl
3133
+ };
3134
+ }
3135
+ /**
3136
+ * True when `config.digitalIds` carries a device-wallet method that is switched
3137
+ * on, honouring both dashboard toggles: the `digitalIds.enabled` master switch
3138
+ * and each method's own `enabled` flag. An absent `enabled` counts as on, the
3139
+ * tolerant reading the rest of this module uses.
3140
+ *
3141
+ * Both toggles are applied by `normalizeDigitalIdMethods`, so this only has to
3142
+ * ask whether any surviving method is a wallet.
3143
+ */
3144
+ function hasEnabledDeviceWalletMethod(config) {
3145
+ return normalizeDigitalIdMethods(config).some(isDeviceWalletMethod);
3146
+ }
3147
+ /**
3148
+ * Resolves the implementation that should fulfil a digital-ID document type:
3149
+ * the first funnel entry that is active under the given config and platform.
3150
+ * `undefined` when nothing can run it (the chooser then shows no tile for it).
3151
+ */
3152
+ function resolveDigitalIdImplementation(docType, config, platform) {
3153
+ for (const entry of DIGITAL_ID_REGISTRY[docType]) {
3154
+ if (!isEntryActive(entry, config, platform)) continue;
3155
+ if (entry.kind === "incodeWallet") return { kind: "incodeWallet" };
3156
+ const method = findConfiguredMethod(entry, config);
3157
+ return {
3158
+ kind: "trinsicRedirect",
3159
+ providerId: entry.providerId,
3160
+ method: method ? toMappedMethod(entry, method) : void 0
3161
+ };
3162
+ }
3163
+ }
3164
+ //#endregion
3165
+ //#region src/modules/id/idCaptureHelpers.ts
3166
+ /**
3167
+ * Normalizes a configured method into the mapped redirect shape for a
3168
+ * resolved provider. The already-mapped shape is returned as-is; the raw
3169
+ * backend shape gets `provider` + `WEB_REDIRECT` filled in.
3170
+ */
3171
+ function toRedirectMethod(method, provider) {
3172
+ if (method.provider && method.flowType) return method;
3204
3173
  return {
3205
- sharpness: result.sharpness ?? 0,
3206
- correctSharpness: result.sharpness != null && result.sharpness >= 10,
3207
- glare: result.glare ?? 0,
3208
- correctGlare: result.glare != null && result.glare >= 10,
3209
- horizontalResolution: 0,
3210
- classification: result.classification === true,
3211
- readability: result.readability === true,
3212
- typeOfId: result.typeOfId ?? "",
3213
- issueYear: 0,
3214
- issueName: "",
3215
- sessionStatus: result.sessionStatus ?? "",
3216
- countryCode: result.countryCode ?? "",
3217
- skipBackIdCapture: result.skipBackIdCapture,
3218
- skipGlareFront: result.skipGlareFront,
3219
- skipGlareBack: result.skipGlareBack,
3220
- acceptedDocuments: result.acceptedDocuments ? [...result.acceptedDocuments] : void 0,
3221
- forceFrontIdCapture: result.forceFrontIdCapture,
3222
- idValidationCompleted: result.idValidationCompleted,
3223
- showMandatoryConsent: result.showMandatoryConsent,
3224
- regulationType,
3225
- failReason: result.failReason,
3226
- side: result.side,
3227
- idQualityAttemptApproved: result.idQualityAttemptApproved,
3228
- isDocumentExpired: result.isDocumentExpired,
3229
- success: result.success
3174
+ method: method.method,
3175
+ provider,
3176
+ flowType: "WEB_REDIRECT",
3177
+ country: method.country ?? method.countries?.[0],
3178
+ displayName: method.displayName,
3179
+ logoUrl: method.logoUrl
3230
3180
  };
3231
3181
  }
3232
- function getProtectedIdUploadRequest(context, sessionToken = getToken()) {
3233
- const target = resolveProtectedIdTarget(context.config, context.currentMode);
3234
- const front = context.currentMode !== "back";
3235
- const params = {};
3236
- if (front && context.config.onlyFront === true) params.onlyFront = true;
3237
- if (front && context.config.extractIdFace === false) params.extractIdFace = false;
3238
- const sharpness = context.qualityElements?.sharpness;
3239
- if (sharpness !== void 0 && sharpness !== 0 && Number.isFinite(sharpness)) params.sharpness = getRealQualityValue(sharpness);
3240
- const config = {
3241
- ...sessionToken ? { headers: { "X-Incode-Hardware-Id": sessionToken } } : {},
3242
- ...Object.keys(params).length > 0 ? { params } : {}
3182
+ /**
3183
+ * The enabled web-redirect digital-ID method to launch — the one matching
3184
+ * `providerId` when given (a chooser card was picked), else the first
3185
+ * configured redirect method — or `undefined` when none is configured.
3186
+ * Drives the provider and country passed to the `digitalIdRedirectMachine`.
3187
+ *
3188
+ * Accepts both the mapped array (`IdCaptureDigitalIdMethod[]`) and the raw
3189
+ * backend `configuration.digitalIds` object (`{ enabled, methods: [...] }`);
3190
+ * for the raw shape the provider comes from the backend-enriched
3191
+ * `providerId`/`flowType` when present, else from the persisted method name
3192
+ * via the digital-ID registry (`IDIN` → `trinsic_netherlands_idin`).
3193
+ */
3194
+ function getDigitalIdRedirectMethod(config, providerId) {
3195
+ for (const method of normalizeDigitalIdMethods(config)) {
3196
+ const provider = resolveRedirectProviderId(method);
3197
+ if (!provider || providerId !== void 0 && provider !== providerId) continue;
3198
+ return toRedirectMethod(method, provider);
3199
+ }
3200
+ }
3201
+ /**
3202
+ * The digital-ID web-redirect providers that get their own chooser card: one
3203
+ * per enabled redirect method in `config.digitalIds`, in backend (dashboard)
3204
+ * order, deduped by provider. Wallet-funnel fallbacks (the Trinsic mDL leg)
3205
+ * are excluded — they surface through the Digital ID tile instead.
3206
+ *
3207
+ * Card display data (`displayName`, `logoUrl`, `country`) is whatever the
3208
+ * backend enriched the method with; the UI falls back to generic copy / icon
3209
+ * when absent.
3210
+ */
3211
+ function getAvailableDigitalIdRedirectOptions(config) {
3212
+ const options = [];
3213
+ for (const method of normalizeDigitalIdMethods(config)) {
3214
+ const providerId = resolveRedirectProviderId(method);
3215
+ if (!providerId || !isStandaloneRedirectProvider(providerId) || options.some((option) => option.providerId === providerId)) continue;
3216
+ options.push({
3217
+ providerId,
3218
+ method: method.method,
3219
+ displayName: method.displayName,
3220
+ logoUrl: method.logoUrl,
3221
+ country: method.country ?? method.countries?.[0]
3222
+ });
3223
+ }
3224
+ return options;
3225
+ }
3226
+ /**
3227
+ * Map of config properties received from the backend to adjusted, internal document types.
3228
+ */
3229
+ const DOCUMENT_TYPES_CONFIG_TO_ADJUSTED_MAP = [
3230
+ ["enableId", "id"],
3231
+ ["enablePassport", "passport"],
3232
+ ["digitalIdsUpload", "digitalIdUpload"]
3233
+ ];
3234
+ /**
3235
+ * Returns the list of document types enabled by config for the chooser screen, in display order.
3236
+ * Used to populate chooser state so UI and headless consumers can render the correct options.
3237
+ *
3238
+ * Digital-ID document types (device wallet, Trinsic web-redirect) come from
3239
+ * the digital-ID registry funnel: each registry document type contributes the
3240
+ * tile of its first active implementation. A `deviceWallet: true` config on a
3241
+ * platform without the Credentials API therefore shows the Trinsic fallback
3242
+ * tile when one is configured — or no tile at all, instead of a dead-end one.
3243
+ *
3244
+ * @param config - ID capture configuration (from flow or init)
3245
+ * @param platform - Platform capabilities consulted by the digital-ID funnel.
3246
+ * Defaults to no Credentials API for pure-node callers; the machine passes
3247
+ * the real capability check.
3248
+ * @returns Array of IdDocumentType values that should be shown in the document type chooser
3249
+ */
3250
+ function getAvailableDocumentTypes(config, platform = { credentialsAvailable: false }) {
3251
+ const adjustedDocumentTypes = [];
3252
+ for (const [configProperty, adjustedType] of DOCUMENT_TYPES_CONFIG_TO_ADJUSTED_MAP) if (config[configProperty]) adjustedDocumentTypes.push(adjustedType);
3253
+ for (const docType of DIGITAL_ID_DOCUMENT_TYPES) {
3254
+ const route = resolveDigitalIdImplementation(docType, config, platform);
3255
+ if (!route) continue;
3256
+ const documentType = route.kind === "incodeWallet" ? "digitalId" : "digitalIdRedirect";
3257
+ if (!adjustedDocumentTypes.includes(documentType)) adjustedDocumentTypes.push(documentType);
3258
+ }
3259
+ if (!adjustedDocumentTypes.includes("digitalIdRedirect") && getAvailableDigitalIdRedirectOptions(config).length > 0) adjustedDocumentTypes.push("digitalIdRedirect");
3260
+ return adjustedDocumentTypes;
3261
+ }
3262
+ /**
3263
+ * Computes `idCaptureMachine`'s terminal output — the downstream module-skip
3264
+ * signal read by the Flow orchestrator (via `runChildModule`'s `MODULE_COMPLETE`).
3265
+ *
3266
+ * `skipFaceMatch` is set ONLY for the US Smart Capture *camera back-only* path:
3267
+ * a back image was captured on-device (`capturedImages.back`) and no front was
3268
+ * (`!capturedImages.front`) — "no front, no ID face, no match".
3269
+ *
3270
+ * Requiring a captured back is deliberate and load-bearing: the digital-ID
3271
+ * upload path submits a full ID (front face present) but does NOT
3272
+ * populate `capturedImages` (only the camera path does). Gating on a captured
3273
+ * back excludes those upload paths so a real ID-vs-selfie match is never
3274
+ * silently skipped. `capturedImages.front`/`forceFrontIdCapture` alone are not
3275
+ * enough (a later front upload clears the request; uploads set neither field).
3276
+ *
3277
+ * Always `false` outside US Smart Capture. The selfie/face capture itself is
3278
+ * unaffected — only the ID-vs-selfie match is skipped.
3279
+ *
3280
+ * Also `true` on the Digital ID web-redirect (Trinsic iDIN/FTN) COMPLETED path
3281
+ * when the scheme returned no portrait (`isDigitalIdRedirect && !portraitPresent`,
3282
+ * FM-1/FM-2): there is no ID face to match the selfie against.
3283
+ */
3284
+ function computeIdCaptureOutput(context) {
3285
+ const frontIdCaptured = Boolean(context.capturedImages.front);
3286
+ const backIdCaptured = Boolean(context.capturedImages.back);
3287
+ return {
3288
+ frontIdCaptured,
3289
+ skipFaceMatch: context.isDigitalIdRedirect === true && context.portraitPresent !== true || context.config.usSmartCapture === true && backIdCaptured && !frontIdCaptured
3243
3290
  };
3244
- return Object.keys(config).length > 0 ? {
3245
- target,
3246
- config
3247
- } : { target };
3248
3291
  }
3249
3292
  //#endregion
3250
3293
  //#region src/modules/digital-id-redirect/pollCadence.ts
@@ -3765,9 +3808,6 @@ function getIdErrorCodeFromUnknown(error) {
3765
3808
  function asPublicIdEvent(event) {
3766
3809
  return event;
3767
3810
  }
3768
- function isCaptureOnlyConfig(config) {
3769
- return "onCapture" in config && typeof config.onCapture === "function";
3770
- }
3771
3811
  function protectedFailure(event) {
3772
3812
  return event.type === "PROTECTED_ATTEMPT_FAILED" ? event : null;
3773
3813
  }
@@ -3781,13 +3821,13 @@ const idCaptureMachine = setup({
3781
3821
  actors: {
3782
3822
  checkPermission: checkPermissionActor,
3783
3823
  requestPermission: requestPermissionActor,
3824
+ requestMotionPermission: requestMotionPermissionActor,
3784
3825
  protectedAttempt: createProtectedIdAttemptActor(),
3785
3826
  protectedControllerLifetime: createProtectedIdControllerLifetimeActor(),
3786
3827
  captureOnlyDelivery: createIdCaptureOnlyDeliveryActor(),
3787
3828
  uploadProgressTicker: uploadProgressTickerActor,
3788
3829
  processId: processIdActor,
3789
3830
  submitBarcode: submitBarcodeActor,
3790
- manualUploadMachine,
3791
3831
  digitalUploadMachine,
3792
3832
  digitalIdRedirectMachine,
3793
3833
  deviceWalletMachine,
@@ -3804,7 +3844,7 @@ const idCaptureMachine = setup({
3804
3844
  return {};
3805
3845
  }),
3806
3846
  startProtectedMetadataPreparation: ({ context, self }) => {
3807
- if (!isCaptureOnlyConfig(context.config)) getIdProtectedCaptureController(self).startMetadataPreparation(context);
3847
+ if (!hasOnCaptureCallback(context.config)) getIdProtectedCaptureController(self).startMetadataPreparation(context);
3808
3848
  },
3809
3849
  bindProtectedControllerLifetime: sendTo("protectedControllerLifetime", ({ self }) => ({
3810
3850
  type: "BIND_PROTECTED_ID_CONTROLLER",
@@ -3845,7 +3885,7 @@ const idCaptureMachine = setup({
3845
3885
  requestProtectedManualCommit: sendTo("protectedAttempt", { type: "MANUAL_CAPTURE" }),
3846
3886
  finalizeProtectedAttempt: sendTo("protectedAttempt", { type: "FINALIZE" }),
3847
3887
  requestProtectedUpload: enqueueActions(({ context, enqueue }) => {
3848
- if (isCaptureOnlyConfig(context.config)) return;
3888
+ if (hasOnCaptureCallback(context.config)) return;
3849
3889
  enqueue(sendTo("protectedAttempt", {
3850
3890
  type: "UPLOAD_PROTECTED",
3851
3891
  request: getProtectedIdUploadRequest(context)
@@ -3863,6 +3903,25 @@ const idCaptureMachine = setup({
3863
3903
  type: "RELEASE",
3864
3904
  reason: "retry"
3865
3905
  }),
3906
+ pauseProtectedDetection: sendTo("protectedAttempt", { type: "PAUSE_DETECTION" }),
3907
+ resumeProtectedDetection: sendTo("protectedAttempt", ({ context }) => ({
3908
+ type: "RESUME_DETECTION",
3909
+ reset: !context.manualCaptureTriggered
3910
+ })),
3911
+ resetAutomaticCapture: assign({
3912
+ manualCaptureTriggered: false,
3913
+ detectionStatus: "idle",
3914
+ counterValue: 0,
3915
+ decodedBarcode: null,
3916
+ barcodeFastPathAttempted: false,
3917
+ qualityElements: void 0
3918
+ }),
3919
+ trackSwitchToAutoCapture: ({ context }) => {
3920
+ addEvent({
3921
+ code: context.currentMode === "back" ? "switchToAutoCaptureBackId" : "switchToAutoCaptureFrontId",
3922
+ module: eventModuleNames.idCapture
3923
+ });
3924
+ },
3866
3925
  forwardProtectedBackground: sendTo("protectedAttempt", { type: "BACKGROUND" }),
3867
3926
  forwardProtectedForeground: sendTo("protectedAttempt", { type: "FOREGROUND" }),
3868
3927
  storeProtectedAutoQuality: assign(({ event }) => event.type === "PROTECTED_AUTO_CAPTURE" ? { qualityElements: event.qualityElements } : {}),
@@ -4047,9 +4106,12 @@ const idCaptureMachine = setup({
4047
4106
  performStopMediaRecording(context);
4048
4107
  },
4049
4108
  clearRecordingSession: assign({ recordingSession: () => void 0 }),
4050
- setSelectedDocument: assign({ selectedDocumentType: ({ event }) => {
4051
- if ("documentType" in event) return event.documentType;
4052
- } }),
4109
+ setSelectedDocument: assign({
4110
+ selectedDocumentType: ({ event }) => {
4111
+ if ("documentType" in event) return event.documentType;
4112
+ },
4113
+ selectedDigitalIdProviderId: ({ event }) => event.type === "SELECT_DOCUMENT" && event.documentType === "digitalIdRedirect" ? event.providerId : void 0
4114
+ }),
4053
4115
  setCurrentMode: assign({ currentMode: ({ event, context }) => getCurrentModeFromEvent(context, asPublicIdEvent(event)) }),
4054
4116
  storeCapturedImage: assign(({ context, event }) => getStoreCapturedImageValues(context, asPublicIdEvent(event))),
4055
4117
  setCandidateFrame: assign(({ context, event }) => getCandidateFrameValues(context, asPublicIdEvent(event))),
@@ -4190,6 +4252,22 @@ const idCaptureMachine = setup({
4190
4252
  digitalIdResumeProviderId: pending?.providerId
4191
4253
  };
4192
4254
  }),
4255
+ /**
4256
+ * Drops the inputs that belong to one digital-ID attempt: the return-leg
4257
+ * params and the recovered verificationId/providerId, plus the funnel
4258
+ * route. The `digitalIdRedirect` child copies what it needs on entry, so
4259
+ * once it exits nothing reads them — but left in place they are replayed
4260
+ * on the next redirect selection. The return leg would re-submit the
4261
+ * previous provider's result (the user picks iDIN and gets FTN's
4262
+ * cancelled screen back), and a stale route would override the card the
4263
+ * user just picked.
4264
+ */
4265
+ clearDigitalIdAttempt: assign({
4266
+ digitalIdReturnParams: () => void 0,
4267
+ digitalIdResumeVerificationId: () => void 0,
4268
+ digitalIdResumeProviderId: () => void 0,
4269
+ digitalIdRoute: () => void 0
4270
+ }),
4193
4271
  incrementProcessingExpiredAttempts: assign(({ context }) => ({ processingExpiredAttempts: context.processingExpiredAttempts + 1 })),
4194
4272
  setSkipProcessId: assign({ skipProcessId: () => true }),
4195
4273
  setExpiredFromUpload: assign({ expiredFromUpload: () => true }),
@@ -4199,7 +4277,7 @@ const idCaptureMachine = setup({
4199
4277
  clearExpiredFromUpload: assign({ expiredFromUpload: () => false })
4200
4278
  },
4201
4279
  guards: {
4202
- isCaptureOnlyDelivery: ({ context }) => isCaptureOnlyConfig(context.config),
4280
+ isCaptureOnlyDelivery: ({ context }) => hasOnCaptureCallback(context.config),
4203
4281
  isProtectedUploadFailure: ({ event }) => protectedFailure(event)?.failure.stage === "upload",
4204
4282
  isProtectedManualRefusal: ({ event }) => protectedFailure(event)?.failure.stage === "manualCommit",
4205
4283
  hasShowTutorial: hasShowTutorialGuard,
@@ -4216,6 +4294,7 @@ const idCaptureMachine = setup({
4216
4294
  }),
4217
4295
  isPermissionDeniedError: ({ event }) => isPermissionDeniedErrorGuard({ event: asPublicIdEvent(event) }),
4218
4296
  isPermissionBlocked: isPermissionBlockedGuard,
4297
+ requiresMotionPermissionUserGesture: requiresMotionPermissionUserGestureGuard,
4219
4298
  hasAttemptsRemaining: hasAttemptsRemainingGuard,
4220
4299
  hasCapturedImage: hasCapturedImageGuard,
4221
4300
  hasCandidateFrame: ({ event }) => hasCandidateFrameGuard({ event: asPublicIdEvent(event) }),
@@ -4229,9 +4308,7 @@ const idCaptureMachine = setup({
4229
4308
  isDeepsightEnabled: isDeepsightEnabledGuard,
4230
4309
  isDeepsightReady: isDeepsightReadyGuard,
4231
4310
  needsDeepsightInit: needsDeepsightInitGuard,
4232
- hasOnlyManualUploadAvailable: hasOnlyManualUploadAvailableGuard,
4233
4311
  hasNoDocumentTypesAvailable: hasNoDocumentTypesAvailableGuard,
4234
- isManualIdUploadSelected: ({ event }) => isManualIdUploadSelectedGuard({ event: asPublicIdEvent(event) }),
4235
4312
  isDigitalIdUploadSelected: ({ event }) => isDigitalIdUploadSelectedGuard({ event: asPublicIdEvent(event) }),
4236
4313
  isDigitalIdRedirectSelected: ({ event }) => isDigitalIdRedirectSelectedGuard({ event: asPublicIdEvent(event) }),
4237
4314
  isDigitalIdSelected: ({ event }) => isDigitalIdSelectedGuard({ event: asPublicIdEvent(event) }),
@@ -4254,7 +4331,6 @@ const idCaptureMachine = setup({
4254
4331
  forceFinishAfterProcessing: forceFinishAfterProcessingGuard,
4255
4332
  isBackModeInMultiSideFlow: isBackModeInMultiSideFlowGuard,
4256
4333
  wasExpiredRedirected: wasExpiredRedirectedGuard,
4257
- isManualUploadExhausted: ({ event }) => event.output?.result === "exhausted",
4258
4334
  isDigitalUploadExhausted: ({ event }) => event.output?.result === "exhausted",
4259
4335
  isDigitalUploadSuccessful: ({ event }) => event.output?.result === "success",
4260
4336
  isDigitalIdRedirectCompleted: ({ event }) => event.output?.status === "COMPLETED",
@@ -4303,9 +4379,13 @@ const idCaptureMachine = setup({
4303
4379
  },
4304
4380
  context: ({ input, self }) => {
4305
4381
  attachIdProtectedCaptureController(self, createIdProtectedCaptureController());
4382
+ const config = {
4383
+ ...input.config,
4384
+ captureAttempts: input.config.captureAttempts ?? 1
4385
+ };
4306
4386
  const currentMode = input.config.onlyBack ? "back" : !input.config.enableId && input.config.enablePassport ? "passport" : input.config.usSmartCapture ? "back" : "front";
4307
4387
  return {
4308
- config: input.config,
4388
+ config,
4309
4389
  captureFeedbackMode: input.captureFeedbackMode ?? "countdown",
4310
4390
  captureWindowMs: input.captureWindowMs,
4311
4391
  optimizingDwellMs: input.optimizingDwellMs,
@@ -4325,7 +4405,7 @@ const idCaptureMachine = setup({
4325
4405
  barcodeFastPathAttempted: false,
4326
4406
  barcodeFastPathCaptured: false,
4327
4407
  recordingSession: void 0,
4328
- attemptsRemaining: input.config.captureAttempts,
4408
+ attemptsRemaining: config.captureAttempts,
4329
4409
  expiredAttempts: 0,
4330
4410
  expiredRedirected: false,
4331
4411
  processingExpiredAttempts: 0,
@@ -4359,6 +4439,8 @@ const idCaptureMachine = setup({
4359
4439
  disableIpify: getDisableIpify(),
4360
4440
  deepsightInitAttempted: false,
4361
4441
  availableDocumentTypes: getAvailableDocumentTypes(input.config, { credentialsAvailable: input.dependencies?.credentials?.isAvailable() ?? false }),
4442
+ digitalIdRedirectOptions: getAvailableDigitalIdRedirectOptions(input.config),
4443
+ selectedDigitalIdProviderId: void 0,
4362
4444
  captureResolution: void 0,
4363
4445
  cameraFallbackLevel: void 0,
4364
4446
  usingBackCamera: void 0
@@ -4379,10 +4461,6 @@ const idCaptureMachine = setup({
4379
4461
  guard: "hasNoDocumentTypesAvailable",
4380
4462
  actions: [() => console.error(ID_CAPTURE_NO_DOCUMENT_TYPES_ERROR), assign({ error: () => ID_CAPTURE_NO_DOCUMENT_TYPES_ERROR })]
4381
4463
  },
4382
- {
4383
- target: "manualIdUpload",
4384
- guard: "hasOnlyManualUploadAvailable"
4385
- },
4386
4464
  {
4387
4465
  target: "chooser",
4388
4466
  guard: "hasShowDocumentChooser"
@@ -4413,11 +4491,6 @@ const idCaptureMachine = setup({
4413
4491
  guard: "isDigitalIdUploadSelected",
4414
4492
  actions: ["setSelectedDocument"]
4415
4493
  },
4416
- {
4417
- target: "manualIdUpload",
4418
- guard: "isManualIdUploadSelected",
4419
- actions: ["setSelectedDocument"]
4420
- },
4421
4494
  {
4422
4495
  target: "ageVerification",
4423
4496
  guard: "hasAgeAssurance",
@@ -4494,6 +4567,14 @@ const idCaptureMachine = setup({
4494
4567
  }]
4495
4568
  },
4496
4569
  on: {
4570
+ CONTINUE_TO_PERMISSION: [{
4571
+ target: "requestingMotionPermission",
4572
+ guard: "requiresMotionPermissionUserGesture",
4573
+ actions: "trackContinue"
4574
+ }, {
4575
+ target: "#idCapture.permissions",
4576
+ actions: "trackContinue"
4577
+ }],
4497
4578
  NEXT_STEP: {
4498
4579
  target: "#idCapture.capture",
4499
4580
  actions: "trackContinue"
@@ -4506,6 +4587,14 @@ const idCaptureMachine = setup({
4506
4587
  },
4507
4588
  initializingCamera: {
4508
4589
  type: "parallel",
4590
+ on: { CONTINUE_TO_PERMISSION: [{
4591
+ target: "requestingMotionPermission",
4592
+ guard: "requiresMotionPermissionUserGesture",
4593
+ actions: "trackContinue"
4594
+ }, {
4595
+ target: "#idCapture.permissions",
4596
+ actions: "trackContinue"
4597
+ }] },
4509
4598
  states: {
4510
4599
  cameraInit: {
4511
4600
  initial: "initializingStream",
@@ -4547,6 +4636,14 @@ const idCaptureMachine = setup({
4547
4636
  onDone: { target: "#idCapture.capture" }
4548
4637
  },
4549
4638
  ready: { on: {
4639
+ CONTINUE_TO_PERMISSION: [{
4640
+ target: "requestingMotionPermission",
4641
+ guard: "requiresMotionPermissionUserGesture",
4642
+ actions: "trackContinue"
4643
+ }, {
4644
+ target: "#idCapture.permissions",
4645
+ actions: "trackContinue"
4646
+ }],
4550
4647
  NEXT_STEP: {
4551
4648
  target: "waitingForPermission",
4552
4649
  actions: "trackContinue"
@@ -4587,6 +4684,15 @@ const idCaptureMachine = setup({
4587
4684
  actions: assign({ permissionResult: ({ event }) => event.output })
4588
4685
  }
4589
4686
  ]
4687
+ } },
4688
+ requestingMotionPermission: { invoke: {
4689
+ id: "requestTutorialMotionPermission",
4690
+ src: "requestMotionPermission",
4691
+ onDone: [{
4692
+ target: "#idCapture.permissions.denied",
4693
+ guard: ({ event }) => event.output === "denied"
4694
+ }, { target: "#idCapture.permissions" }],
4695
+ onError: { target: "#idCapture.permissions.denied" }
4590
4696
  } }
4591
4697
  }
4592
4698
  },
@@ -4642,6 +4748,7 @@ const idCaptureMachine = setup({
4642
4748
  } },
4643
4749
  motionOnly: { on: {
4644
4750
  REQUEST_PERMISSION: "requesting",
4751
+ GO_TO_LEARN_MORE: "learnMore",
4645
4752
  BACK: {
4646
4753
  target: "#idCapture.tutorial.ready",
4647
4754
  guard: "hasShowTutorial"
@@ -4755,6 +4862,7 @@ const idCaptureMachine = setup({
4755
4862
  }],
4756
4863
  entry: [assign({ detectionStatus: () => "detecting" })],
4757
4864
  on: {
4865
+ OPEN_HELP: { target: "help" },
4758
4866
  DETECTION_UPDATE: { actions: "setDetectionStatus" },
4759
4867
  PROTECTED_AUTO_CAPTURE: { actions: ["storeProtectedAutoQuality", "requestProtectedAutoCommit"] },
4760
4868
  PROTECTED_AUTO_COMMITTED_SIGNAL: {
@@ -4819,6 +4927,7 @@ const idCaptureMachine = setup({
4819
4927
  initial: "searching",
4820
4928
  states: {
4821
4929
  searching: { on: {
4930
+ OPEN_HELP: { target: "#idCapture.capture.help" },
4822
4931
  DETECTION_UPDATE: { actions: "setDetectionStatus" },
4823
4932
  ID_SIDE_CHANGE: { actions: assign({ detectionStatus: ({ context, event }) => getDetectionStatusFromSideChange(context, event.side) }) },
4824
4933
  BARCODE_DECODED: [{
@@ -4856,7 +4965,34 @@ const idCaptureMachine = setup({
4856
4965
  }
4857
4966
  }
4858
4967
  },
4968
+ help: {
4969
+ entry: "pauseProtectedDetection",
4970
+ on: {
4971
+ CLOSE_HELP: [{
4972
+ guard: "hasManualCaptureTriggered",
4973
+ target: "manualCaptureWaiting",
4974
+ actions: "resumeProtectedDetection"
4975
+ }, {
4976
+ target: "choosingDetectionStrategy",
4977
+ actions: ["resetAutomaticCapture", "resumeProtectedDetection"]
4978
+ }],
4979
+ SWITCH_TO_MANUAL_CAPTURE: { actions: assign({
4980
+ manualCaptureTriggered: true,
4981
+ detectionStatus: "manualCapture"
4982
+ }) },
4983
+ SWITCH_TO_AUTO_CAPTURE: {
4984
+ guard: "hasManualCaptureTriggered",
4985
+ target: "releasingForRetry",
4986
+ actions: ["trackSwitchToAutoCapture", "resetAutomaticCapture"]
4987
+ }
4988
+ }
4989
+ },
4859
4990
  manualCaptureWaiting: { on: {
4991
+ OPEN_HELP: { target: "help" },
4992
+ SWITCH_TO_AUTO_CAPTURE: {
4993
+ target: "releasingForRetry",
4994
+ actions: ["trackSwitchToAutoCapture", "resetAutomaticCapture"]
4995
+ },
4860
4996
  MANUAL_CAPTURE: { actions: "requestProtectedManualCommit" },
4861
4997
  PROTECTED_MANUAL_COMMITTED_SIGNAL: {
4862
4998
  target: "capturingManual",
@@ -4962,7 +5098,7 @@ const idCaptureMachine = setup({
4962
5098
  id: "captureOnlyDelivery",
4963
5099
  src: "captureOnlyDelivery",
4964
5100
  input: ({ context }) => {
4965
- if (!isCaptureOnlyConfig(context.config)) return { mode: "protected" };
5101
+ if (!hasOnCaptureCallback(context.config)) return { mode: "protected" };
4966
5102
  return { mode: "captureOnly" };
4967
5103
  }
4968
5104
  }],
@@ -5264,35 +5400,6 @@ const idCaptureMachine = setup({
5264
5400
  actions: "resetContext"
5265
5401
  } }
5266
5402
  },
5267
- manualIdUpload: {
5268
- invoke: {
5269
- id: "manualIdUpload",
5270
- src: "manualUploadMachine",
5271
- input: ({ context }) => ({
5272
- isSecondId: context.config.isSecondId ?? false,
5273
- onlyFront: context.config.onlyFront ?? false,
5274
- onlyBack: context.config.onlyBack ?? false,
5275
- captureAttempts: context.config.captureAttempts,
5276
- extractIdFace: context.config.extractIdFace
5277
- }),
5278
- onSnapshot: { actions: () => {} },
5279
- onDone: [{
5280
- guard: "isManualUploadExhausted",
5281
- target: "#idCapture.finished"
5282
- }, { target: "#idCapture.processing" }],
5283
- onError: {
5284
- target: "#idCapture.error",
5285
- actions: assign({ error: ({ event }) => String(event.error) })
5286
- }
5287
- },
5288
- on: {
5289
- QUIT: { target: "closed" },
5290
- MANUAL_UPLOAD_TAB_CHANGED: { actions: sendTo("manualIdUpload", ({ event }) => event) },
5291
- MANUAL_UPLOAD_FILE_SELECTED: { actions: sendTo("manualIdUpload", ({ event }) => event) },
5292
- MANUAL_UPLOAD_CONTINUE: { actions: sendTo("manualIdUpload", ({ event }) => event) },
5293
- MANUAL_UPLOAD_RESET: { actions: sendTo("manualIdUpload", ({ event }) => event) }
5294
- }
5295
- },
5296
5403
  digitalIdUpload: {
5297
5404
  invoke: {
5298
5405
  id: "digitalIdUpload",
@@ -5351,28 +5458,38 @@ const idCaptureMachine = setup({
5351
5458
  },
5352
5459
  {
5353
5460
  target: "chooser",
5354
- guard: "hasShowDocumentChooser"
5461
+ guard: "hasShowDocumentChooser",
5462
+ actions: ["clearDigitalIdAttempt"]
5355
5463
  },
5356
- { target: "loading" }
5464
+ {
5465
+ target: "loading",
5466
+ actions: ["clearDigitalIdAttempt"]
5467
+ }
5357
5468
  ],
5358
5469
  onError: [{
5359
5470
  target: "chooser",
5360
- guard: "hasShowDocumentChooser"
5361
- }, { target: "loading" }]
5471
+ guard: "hasShowDocumentChooser",
5472
+ actions: ["clearDigitalIdAttempt"]
5473
+ }, {
5474
+ target: "loading",
5475
+ actions: ["clearDigitalIdAttempt"]
5476
+ }]
5362
5477
  },
5363
5478
  on: { QUIT: { target: "closed" } }
5364
5479
  },
5365
5480
  digitalIdRedirect: {
5481
+ exit: "clearDigitalIdAttempt",
5366
5482
  invoke: {
5367
5483
  id: "digitalIdRedirect",
5368
5484
  src: "digitalIdRedirectMachine",
5369
5485
  input: ({ context }) => {
5370
- const method = getDigitalIdRedirectMethod(context.config);
5371
5486
  const resuming = context.digitalIdReturnParams !== void 0 && context.digitalIdResumeVerificationId !== void 0;
5372
5487
  const routedProvider = context.digitalIdRoute?.kind === "trinsicRedirect" ? context.digitalIdRoute.providerId : void 0;
5488
+ const presetProvider = (resuming ? context.digitalIdResumeProviderId : void 0) ?? routedProvider ?? context.selectedDigitalIdProviderId;
5489
+ const method = presetProvider !== void 0 ? getDigitalIdRedirectMethod(context.config, presetProvider) : getDigitalIdRedirectMethod(context.config);
5373
5490
  return {
5374
5491
  config: {
5375
- provider: context.digitalIdResumeProviderId ?? routedProvider ?? method?.provider,
5492
+ provider: presetProvider ?? method?.provider,
5376
5493
  returnUrl: context.config.digitalIdReturnUrl,
5377
5494
  country: method?.country
5378
5495
  },
@@ -5479,4 +5596,4 @@ function provideIdProtectedCaptureController(machine, controller) {
5479
5596
  });
5480
5597
  }
5481
5598
  //#endregion
5482
- export { createIdProtectedCaptureController as a, DIGITAL_ID_DOCUMENT_TYPES as c, resolveDigitalIdImplementation as d, MANUAL_UPLOAD_EXHAUSTION_FORWARD_MS as f, resolveWalletPlatform as h, createIdCaptureOnlyDeliveryActor as i, DIGITAL_ID_REGISTRY as l, MANUAL_UPLOAD_MAX_RETRIES as m, provideIdProtectedCaptureController as n, findIdProtectedCaptureController as o, MANUAL_UPLOAD_MAX_FILE_SIZE_BYTES as p, attachIdProtectedCaptureController as r, getManualUploadTabs as s, idCaptureMachine as t, isRedirectFlowType as u };
5599
+ export { mapDeviceWalletScreenName as _, isRedirectFlowType as a, createIdCaptureOnlyDeliveryActor as c, captureOnlyNeedsBack as d, hasOnCaptureCallback as f, BrowserDigitalCredentialsProvider as g, createDeviceWalletManager as h, DIGITAL_ID_REGISTRY as i, createIdProtectedCaptureController as l, renderWallet as m, provideIdProtectedCaptureController as n, resolveDigitalIdImplementation as o, pickIdCaptureOnlyConfig as p, DIGITAL_ID_DOCUMENT_TYPES as r, attachIdProtectedCaptureController as s, idCaptureMachine as t, findIdProtectedCaptureController as u, resolveWalletPlatform as v };