@incodetech/core 2.3.2-rc.4 → 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-B5Fxc_y9.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--p429c2X.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-CWmZMFJH.esm.js → authenticationManager-SaiAxqvS.esm.js} +6 -6
  27. package/dist/{authenticationStateMachine-BQ7ZW-8a.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-uhDPdU6i.d.ts → consentManager-DgzWQpAR.d.ts} +11 -12
  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-BtYvACzG.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-66GzzBTx.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-DLnr2ZtX.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-CsQMgJDL.esm.js → ekybManager-CazShiQz.esm.js} +23 -27
  78. package/dist/{ekybStateMachine-DBeQNOMo.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-Boj_xbGN.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-BKpeQIZu.esm.js → faceCaptureManagerFactory-uFm1AcaY.esm.js} +19 -54
  105. package/dist/{faceCaptureSetup-DOrt8-Sn.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-zY6FcYx_.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-CBa6YyI5.esm.js → flowCompletionService-xnn_SlJU.esm.js} +2 -2
  123. package/dist/{flowServices-JW6uRaUt.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-DD5J1T_p.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-DoelwBqs.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-Chne8cyb.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-0504dfcD.esm.js → idCaptureManager-D4V8vXtY.esm.js} +276 -210
  149. package/dist/{idCaptureServices-B-Ynn67N.esm.js → idCaptureServices-DJ33k3-p.esm.js} +21 -39
  150. package/dist/{idCaptureStateMachine-B7ET-hfq.esm.js → idCaptureStateMachine-PJt0yzom.esm.js} +989 -872
  151. package/dist/{idOcrManager-BF_cXZq8.d.ts → idOcrManager-BTdHlJ9D.d.ts} +3 -4
  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-v2TH5g7e.d.ts → identityReuseManager-ooAbp6Kc.d.ts} +162 -44
  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-BYRnWbIC.d.ts → mandatoryConsentManager-TkX57U6f.d.ts} +8 -9
  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-CZU29-IV.esm.js → runChildModule-vdxQMCmO.esm.js} +13 -12
  193. package/dist/safeStorage-UxDrnjwa.esm.js +35 -0
  194. package/dist/{sdkVersion-DOhxIxQQ.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-CWhbwRdb.esm.js → selfieManager-26pENXxw.esm.js} +6 -6
  200. package/dist/selfieStateMachine-BhfKsl3I.esm.js +41 -0
  201. package/dist/{session-CctUSSCx.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-DmB0JHAB.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-Ce9zO78j.esm.js → thumbmark.esm-CRc0cYvz.esm.js} +33 -776
  217. package/dist/{trueSightService-oMFESSSb.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-D_4NwYM-.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-CtMCi4KZ.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,1389 +1,1431 @@
1
- import { r as WasmUtilProvider } from "./safeStorage-BdU9_m2E.esm.js";
2
- import { t as ensureWasmPipelines } from "./setup-DmB0JHAB.esm.js";
3
- import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
1
+ import { t as WasmUtilProvider } from "./WasmUtilProvider-CIdnE7Fx.esm.js";
4
2
  import { t as api } from "./api-Bx3gKW_y.esm.js";
5
- import { t as endpoints } from "./endpoints-B-3Q-LUw.esm.js";
6
- import { d as facingModeForStep, f as resolveVideoSelfieSteps, p as videoSelfieWasmPipelines } from "./flowServices-JW6uRaUt.esm.js";
7
- import { i as assign, r as sendTo, s as createActor, t as setup, u as createManager } from "./xstate.esm-BuFLFuzz.esm.js";
8
- import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-BXeQ-Ucz.esm.js";
3
+ import { t as ensureWasmPipelines } from "./setup-C38QkrSP.esm.js";
4
+ import { t as endpoints } from "./endpoints-DHzlkQNe.esm.js";
5
+ import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
6
+ import { t as BrowserLazyImportProvider } from "./BrowserLazyImportProvider-BhoA957O.esm.js";
7
+ import { d as facingModeForStep, f as resolveVideoSelfieSteps, p as videoSelfieWasmPipelines } from "./flowServices-DA64AEZC.esm.js";
8
+ import { i as assign, r as sendTo, s as createActor, t as setup, u as createManager } from "./xstate.esm-DSajW7CJ.esm.js";
9
+ import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-8Gzepa_A.esm.js";
9
10
  import { t as BrowserVisibilityObserverProvider } from "./BrowserVisibilityObserverProvider-BrJN1B8A.esm.js";
10
- import { i as createRecordingCompositor, n as WorkerEncoderRecordingProvider, t as MultipartRecordingService } from "./multipartRecordingService-mwKFTlhR.esm.js";
11
- import { t as VIDEO_SELFIE_VALIDATION_ATTEMPTS_EXHAUSTED_CODE } from "./advanceableErrors-CpF2cBy6.esm.js";
12
- import { a as isConnectionError, n as defaultAcquireCamera, r as createDefaultStepDetector, t as createVideoSelfieCameraWarmer } from "./videoSelfieCameraWarmer-D_4NwYM-.esm.js";
13
- import { t as videoSelfieFlowModuleMachine } from "./videoSelfieFlowModuleMachine-BUe2uACW.esm.js";
14
- //#region src/modules/video-selfie/videoSelfieWasm.ts
15
- function warmupVideoSelfieWasm(config, steps = resolveVideoSelfieSteps(config)) {
16
- return ensureWasmPipelines(videoSelfieWasmPipelines(config, steps));
17
- }
11
+ import { i as createRecordingCompositor, n as WorkerEncoderRecordingProvider, t as MultipartRecordingService } from "./multipartRecordingService-DbeQpXdU.esm.js";
12
+ import { t as VIDEO_SELFIE_VALIDATION_ATTEMPTS_EXHAUSTED_CODE } from "./advanceableErrors-q_pfU3Cn.esm.js";
13
+ import { a as isConnectionError, n as defaultAcquireCamera, r as createDefaultStepDetector, t as createVideoSelfieCameraWarmer } from "./videoSelfieCameraWarmer-C366RKqu.esm.js";
14
+ import { t as videoSelfieFlowModuleMachine } from "./videoSelfieFlowModuleMachine-BCFVHBQ1.esm.js";
15
+ //#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/wave-encoder/index.js
16
+ var wave_encoder_default = () => {
17
+ let BYTES_PER_SAMPLE = 2;
18
+ let recorded = [];
19
+ function encode(buffer) {
20
+ let length = buffer.length;
21
+ let data = new Uint8Array(length * BYTES_PER_SAMPLE);
22
+ for (let i = 0; i < length; i++) {
23
+ let index = i * BYTES_PER_SAMPLE;
24
+ let sample = buffer[i];
25
+ if (sample > 1) sample = 1;
26
+ else if (sample < -1) sample = -1;
27
+ sample = sample * 32768;
28
+ data[index] = sample;
29
+ data[index + 1] = sample >> 8;
30
+ }
31
+ recorded.push(data);
32
+ }
33
+ function dump(sampleRate) {
34
+ let bufferLength = recorded.length ? recorded[0].length : 0;
35
+ let length = recorded.length * bufferLength;
36
+ let wav = new Uint8Array(44 + length);
37
+ let view = new DataView(wav.buffer);
38
+ view.setUint32(0, 1380533830, false);
39
+ view.setUint32(4, 36 + length, true);
40
+ view.setUint32(8, 1463899717, false);
41
+ view.setUint32(12, 1718449184, false);
42
+ view.setUint32(16, 16, true);
43
+ view.setUint16(20, 1, true);
44
+ view.setUint16(22, 1, true);
45
+ view.setUint32(24, sampleRate, true);
46
+ view.setUint32(28, sampleRate * BYTES_PER_SAMPLE, true);
47
+ view.setUint16(32, BYTES_PER_SAMPLE, true);
48
+ view.setUint16(34, 8 * BYTES_PER_SAMPLE, true);
49
+ view.setUint32(36, 1684108385, false);
50
+ view.setUint32(40, length, true);
51
+ for (let i = 0; i < recorded.length; i++) wav.set(recorded[i], i * bufferLength + 44);
52
+ recorded = [];
53
+ postMessage(wav.buffer, [wav.buffer]);
54
+ }
55
+ onmessage = (e) => {
56
+ if (e.data[0] === "encode") encode(e.data[1]);
57
+ else if (e.data[0] === "dump") dump(e.data[1]);
58
+ };
59
+ };
18
60
  //#endregion
19
- //#region src/modules/video-selfie/videoSelfieRecordingMachine.ts
20
- /** A detector that never settles — used when a step has no auto-capture yet. */
21
- const manualStepDetector = () => new Promise(() => {});
22
- function eventOutputStream(event) {
23
- return event.output;
24
- }
25
- function recordingIdFromEvent(event) {
26
- return event.output.recordingId;
27
- }
28
- function errorFromEvent(event) {
29
- return String(event.error);
30
- }
31
- /**
32
- * Chromium reports a camera the device cannot open right now as
33
- * `NotReadableError` ("Could not start video source") or `AbortError`
34
- * ("Starting videoinput failed") — the same pair the rear-camera acquire
35
- * retries on. Anything else (permission, no device) will not clear by
36
- * releasing our own camera.
37
- */
38
- function isCameraBusyError(error) {
39
- const name = typeof error === "object" && error !== null && "name" in error ? error.name : void 0;
40
- return name === "NotReadableError" || name === "AbortError";
41
- }
42
- function detectionStatusFromEvent(event) {
43
- return event.status;
44
- }
45
- function detectionErrorFromEvent(event) {
46
- return event.error;
47
- }
48
- function validationFailureFromEvent(event) {
49
- return event.failure;
50
- }
51
- function errorKindFromEvent(event) {
52
- return event.kind;
53
- }
54
- function retryableFailureFromEvent(event) {
55
- return event.failure;
56
- }
57
- function orientationFromEvent(event) {
58
- return event.orientation;
59
- }
60
- const SWITCHING_CAMERA_MIN_DWELL_MS = 3e3;
61
- /**
62
- * How long a just-stopped camera gets to release before another one is
63
- * opened (see `releasingCamera`). Same settle the rear-camera acquire uses
64
- * on a busy device; a hardware knob, not a protocol value.
65
- */
66
- const CAMERA_RELEASE_SETTLE_MS = 300;
67
- const ID_FLIP_DWELL_MS = 4e3;
68
- const FINAL_SUCCESS_DWELL_MS = 3e3;
69
- const MAX_VIDEO_LENGTH_ERROR_DWELL_MS = 3e3;
70
- const VALIDATION_FEEDBACK_DWELL_MS = 3e3;
71
- const DEFAULT_ATTEMPTS_PER_STEP = 3;
72
- const DEFAULT_MAX_VIDEO_LENGTH_SECONDS = 300;
73
- const UNLIMITED_ATTEMPTS = -1;
74
- function normalizeAttempts(attempts) {
75
- if (attempts === UNLIMITED_ATTEMPTS) return UNLIMITED_ATTEMPTS;
76
- if (typeof attempts === "number" && attempts > 0) return attempts;
77
- return DEFAULT_ATTEMPTS_PER_STEP;
78
- }
79
- function normalizeMaxVideoLength(seconds) {
80
- if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0) return seconds;
81
- return DEFAULT_MAX_VIDEO_LENGTH_SECONDS;
82
- }
83
- function waitForDwell(ms, signal) {
84
- const timerProvider = BrowserTimerProvider.getInstance();
85
- return new Promise((resolve) => {
86
- const onAbort = () => {
87
- timerProvider.clearTimeout(timer);
88
- resolve(false);
89
- };
90
- const timer = timerProvider.setTimeout(() => {
91
- signal.removeEventListener("abort", onAbort);
92
- resolve(true);
93
- }, ms);
94
- signal.addEventListener("abort", onAbort, { once: true });
95
- if (signal.aborted) onAbort();
96
- });
97
- }
61
+ //#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/index.js
62
+ let AudioContext = window.AudioContext || window.webkitAudioContext;
63
+ let createWorker = (fn) => {
64
+ let js = fn.toString().replace(/^(\(\)\s*=>|function\s*\(\))\s*{/, "").replace(/}$/, "");
65
+ let blob = new Blob([js]);
66
+ return new Worker(URL.createObjectURL(blob));
67
+ };
68
+ let error = (method) => {
69
+ let event = new Event("error");
70
+ event.data = /* @__PURE__ */ new Error("Wrong state for " + method);
71
+ return event;
72
+ };
73
+ let context;
98
74
  /**
99
- * Orchestrates a multi-step Video Selfie session as ONE continuous recording
100
- * that follows the camera between steps AND auto-advances per step:
101
- *
102
- * idle ─START▶ [preparingWasm ─▶] acquiringCamera ─▶ starting
103
- * │
104
- * ▼
105
- * recording ⇄ switchingCamera
106
- * │ │ busy camera (ENG-57332)
107
- * │ ▼
108
- * │ releasingCamera ─settle▶ retry once
109
- * │ (capture validated → DETECTION_DONE,
110
- * │ or manual NEXT_STEP)
111
- * last step ─▶ finalizing ─▶ finished
112
- *
113
- * While `recording`, a per-step detector runs WASM auto-capture + backend
114
- * validation; success advances the step (switching the camera when the next
115
- * step's facing differs), and the final step finalizes the multipart upload.
75
+ * Audio Recorder with MediaRecorder API.
116
76
  *
117
- * Teardown: `ABORT` is accepted from ANY non-final state and every settled
118
- * path runs `cleanup`, so no path leaves camera or mic tracks running.
119
- * XState v5 executes NO exit actions on an external
120
- * `actor.stop()`, so the manager sends `ABORT` before stopping the actor —
121
- * never stop this actor directly without aborting first.
77
+ * @example
78
+ * navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
79
+ * let recorder = new MediaRecorder(stream)
80
+ * })
122
81
  */
123
- const videoSelfieRecordingSetup = setup({
124
- types: {
125
- context: {},
126
- events: {},
127
- input: {}
128
- },
129
- delays: {
130
- ID_FLIP_DWELL: ID_FLIP_DWELL_MS,
131
- FINAL_SUCCESS_DWELL: FINAL_SUCCESS_DWELL_MS,
132
- MAX_VIDEO_LENGTH_ERROR_DWELL: MAX_VIDEO_LENGTH_ERROR_DWELL_MS,
133
- VALIDATION_FEEDBACK_DWELL: VALIDATION_FEEDBACK_DWELL_MS,
134
- CAMERA_RELEASE_SETTLE: CAMERA_RELEASE_SETTLE_MS
135
- },
136
- actors: {
137
- prepareWasm: fromPromise(async ({ input }) => input.ensure()),
138
- visibilityChanges: fromCallback(({ input, sendBack }) => input.visibilityObserver.onVisibilityChange((visible) => {
139
- sendBack({ type: visible ? "FOREGROUND" : "BACKGROUND" });
140
- })),
141
- executionTimer: fromCallback(({ input, sendBack, receive }) => {
142
- let handle;
143
- const cancel = () => {
144
- if (handle !== void 0) {
145
- input.timer.clearTimeout(handle);
146
- handle = void 0;
147
- }
148
- };
149
- receive((event) => {
150
- cancel();
151
- if (event.type === "ARM") handle = input.timer.setTimeout(() => {
152
- handle = void 0;
153
- sendBack({ type: "MAX_VIDEO_LENGTH_REACHED" });
154
- }, event.durationMs);
155
- });
156
- return cancel;
157
- }),
158
- acquireCamera: fromPromise(async ({ input, signal }) => {
159
- const stream = await input.acquire(input.facingMode, input.withAudio);
160
- if (signal.aborted) {
161
- stream.getTracks().forEach((track) => track.stop());
162
- throw new Error("Camera acquire cancelled");
163
- }
164
- return stream;
165
- }),
166
- switchCamera: fromPromise(async ({ input, signal }) => {
167
- const dwellCompleted = waitForDwell(input.minDwellMs, signal);
168
- const stream = await input.acquire(input.facingMode, false);
169
- if (signal.aborted) {
170
- stream.getTracks().forEach((track) => track.stop());
171
- throw new Error("Camera switch cancelled");
172
- }
173
- if (!await dwellCompleted) {
174
- stream.getTracks().forEach((track) => track.stop());
175
- throw new Error("Camera switch cancelled");
176
- }
177
- if (signal.aborted) {
178
- stream.getTracks().forEach((track) => track.stop());
179
- throw new Error("Camera switch cancelled");
180
- }
181
- return stream;
182
- }),
183
- startRecording: fromPromise(async ({ input }) => {
184
- await input.service.start(input.stream);
185
- }),
186
- finalizeRecording: fromPromise(async ({ input }) => input.service.stop()),
187
- pauseCapture: fromPromise(async ({ input }) => {
188
- const paused = input.service.pause();
189
- input.compositor.pause();
190
- return paused;
191
- }),
192
- resumeCompositor: fromPromise(async ({ input, signal }) => input.compositor.resume(signal)),
193
- resumeRecorder: fromPromise(async ({ input }) => input.service.resume()),
194
- detectStep: fromCallback(({ input, sendBack }) => {
195
- const controller = new AbortController();
196
- input.detect({
197
- step: input.step,
198
- stream: input.stream,
199
- signal: controller.signal,
200
- onStatus: (status) => sendBack({
201
- type: "DETECTION_STATUS",
202
- status
203
- }),
204
- onOrientation: (orientation) => sendBack({
205
- type: "ORIENTATION",
206
- orientation
207
- })
208
- }).then((result) => {
209
- if (result.outcome === "retryableFailure") {
210
- sendBack({
211
- type: "RETRYABLE_FAILURE",
212
- failure: result.failure
213
- });
214
- return;
215
- }
216
- if (result.outcome === "validationFailed") {
217
- sendBack({
218
- type: "VALIDATION_FAILED",
219
- failure: result.failure
220
- });
221
- return;
222
- }
223
- sendBack({ type: "DETECTION_DONE" });
224
- }).catch((err) => {
225
- if (!controller.signal.aborted) sendBack({
226
- type: "DETECTION_FAILED",
227
- error: String(err),
228
- kind: isConnectionError(err) ? "connection" : "technical"
229
- });
230
- });
231
- return () => controller.abort();
232
- })
233
- },
234
- actions: {
235
- armExecutionTimer: sendTo("executionTimer", ({ context }) => ({
236
- type: "ARM",
237
- durationMs: context.maxVideoLengthSeconds * 1e3
238
- })),
239
- cancelExecutionTimer: sendTo("executionTimer", { type: "CANCEL" }),
240
- advanceStep: assign({
241
- stepIndex: ({ context }) => context.stepIndex + 1,
242
- attemptsRemaining: ({ context }) => context.attemptsPerStep,
243
- validationFailure: () => void 0,
244
- cameraSwitchRetried: () => false
245
- }),
246
- setDetectionStatus: assign({ detectionStatus: ({ event }) => detectionStatusFromEvent(event) }),
247
- setOrientation: assign({ orientation: ({ event }) => orientationFromEvent(event) }),
248
- setDetectionError: assign({ detectionError: ({ event }) => detectionErrorFromEvent(event) }),
249
- setValidationFailure: assign(({ context, event }) => ({
250
- attemptsRemaining: context.attemptsRemaining === UNLIMITED_ATTEMPTS ? UNLIMITED_ATTEMPTS : Math.max(0, context.attemptsRemaining - 1),
251
- validationFailure: validationFailureFromEvent(event),
252
- detectionStatus: "error"
253
- })),
254
- setUncountedRetryableFailure: assign(({ event }) => ({
255
- validationFailure: retryableFailureFromEvent(event),
256
- detectionStatus: "error"
257
- })),
258
- setErrorFromDetection: assign({
259
- error: ({ event }) => detectionErrorFromEvent(event),
260
- errorKind: ({ event }) => errorKindFromEvent(event)
261
- }),
262
- clearDetectionFeedback: assign({
263
- detectionStatus: () => void 0,
264
- detectionError: () => void 0,
265
- validationFailure: () => void 0,
266
- orientation: () => void 0
267
- }),
268
- markPageHidden: assign({ isPageVisible: () => false }),
269
- markPageVisible: assign({ isPageVisible: () => true }),
270
- prepareRecordingSuspension: assign({
271
- isPageVisible: () => false,
272
- resumeState: () => "recording"
273
- }),
274
- setUploadFailedError: assign({
275
- error: () => "Video upload failed",
276
- errorKind: () => "connection"
277
- }),
278
- setMaxVideoLengthReached: assign({
279
- error: () => "maxVideoLengthReached",
280
- errorKind: () => "maxVideoLengthReached"
281
- }),
282
- setValidationAttemptsExhausted: assign({
283
- error: () => "Video selfie validation attempts exhausted",
284
- errorKind: () => "validationAttemptsExhausted"
285
- }),
286
- resetSession: assign(({ context }) => ({
287
- stepIndex: 0,
288
- cameraSwitchRetried: false,
289
- stream: void 0,
290
- composite: void 0,
291
- detectionStatus: void 0,
292
- orientation: void 0,
293
- detectionError: void 0,
294
- validationFailure: void 0,
295
- attemptsRemaining: context.attemptsPerStep,
296
- recordingId: null,
297
- error: void 0,
298
- errorKind: void 0,
299
- resumeState: "recording"
300
- })),
301
- cleanup: ({ context }) => {
302
- context.service.cleanup();
303
- context.compositor.stop();
304
- context.stream?.getTracks().forEach((track) => track.stop());
305
- },
306
- setTechnicalActorError: assign({
307
- error: ({ event }) => errorFromEvent(event),
308
- errorKind: () => "technical"
309
- }),
310
- setAudioEndedError: assign({
311
- error: () => "Audio track ended while the app was backgrounded",
312
- errorKind: () => "technical"
313
- }),
314
- setCameraResumeError: assign({
315
- error: () => "Camera did not produce a fresh frame after foregrounding",
316
- errorKind: () => "technical"
317
- }),
318
- setRecorderResumeError: assign({
319
- error: () => "Recording could not resume after foregrounding",
320
- errorKind: () => "technical"
321
- }),
322
- setPauseError: assign({
323
- error: () => "Recording could not be paused before backgrounding",
324
- errorKind: () => "technical"
325
- }),
326
- releaseCurrentVideo: ({ context }) => {
327
- context.stream?.getVideoTracks().forEach((track) => track.stop());
328
- }
329
- },
330
- guards: {
331
- shouldEnterWasmGate: ({ context }) => context.ensureWasmReady !== void 0 && context.errorKind !== "maxVideoLengthReached" && context.errorKind !== "validationAttemptsExhausted",
332
- isLastStep: ({ context }) => context.stepIndex >= context.steps.length - 1,
333
- hasAttemptsRemaining: ({ context }) => context.attemptsRemaining === UNLIMITED_ATTEMPTS || context.attemptsRemaining > 0,
334
- nextStepFlipsId: ({ context }) => context.steps[context.stepIndex] === "frontId" && context.steps[context.stepIndex + 1] === "backId",
335
- nextStepFlipsCamera: ({ context }) => facingModeForStep(context.steps[context.stepIndex]) !== facingModeForStep(context.steps[context.stepIndex + 1]),
336
- uploadReturnedNoRecordingId: ({ event }) => recordingIdFromEvent(event) === null,
337
- maxVideoLengthWasReached: ({ context }) => context.errorKind === "maxVideoLengthReached",
338
- validationAttemptsWereExhausted: ({ context }) => context.errorKind === "validationAttemptsExhausted",
339
- errorIsRetryable: ({ context }) => context.errorKind !== "maxVideoLengthReached" && context.errorKind !== "validationAttemptsExhausted",
340
- canRetryCameraSwitchAfterRelease: ({ context, event }) => !context.cameraSwitchRetried && isCameraBusyError(event.error),
341
- pageIsHidden: ({ context }) => !context.isPageVisible,
342
- pauseFailed: ({ event }) => !event.output,
343
- pauseSucceededWhileVisible: ({ context, event }) => context.isPageVisible && event.output,
344
- compositorAudioEnded: ({ event }) => event.output === "audio-ended",
345
- compositorDidNotResume: ({ event }) => event.output !== "resumed",
346
- forceVideoRecovery: ({ context }) => context.resumeState === "switchingCamera",
347
- recorderResumeFailed: ({ event }) => !event.output,
348
- resumeValidationFeedback: ({ context }) => context.resumeState === "validationFeedback",
349
- resumeFlippingId: ({ context }) => context.resumeState === "flippingId"
350
- }
351
- });
352
- const ADVANCE_TRANSITIONS = [
353
- {
354
- target: "finalizing",
355
- guard: "isLastStep"
356
- },
357
- {
358
- target: "flippingId",
359
- guard: "nextStepFlipsId"
360
- },
361
- {
362
- target: "switchingCamera",
363
- guard: "nextStepFlipsCamera",
364
- actions: "advanceStep"
365
- },
366
- {
367
- target: "recording",
368
- reenter: true,
369
- actions: "advanceStep"
82
+ var MediaRecorder = class MediaRecorder {
83
+ /**
84
+ * @param {MediaStream} stream The audio stream to record.
85
+ */
86
+ constructor(stream, config = null) {
87
+ /**
88
+ * The `MediaStream` passed into the constructor.
89
+ * @type {MediaStream}
90
+ */
91
+ this.stream = stream;
92
+ this.config = config;
93
+ /**
94
+ * The current state of recording process.
95
+ * @type {"inactive"|"recording"|"paused"}
96
+ */
97
+ this.state = "inactive";
98
+ this.em = document.createDocumentFragment();
99
+ this.encoder = createWorker(MediaRecorder.encoder);
100
+ let recorder = this;
101
+ this.encoder.addEventListener("message", (e) => {
102
+ let event = new Event("dataavailable");
103
+ event.data = new Blob([e.data], { type: recorder.mimeType });
104
+ recorder.em.dispatchEvent(event);
105
+ if (recorder.state === "inactive") recorder.em.dispatchEvent(new Event("stop"));
106
+ });
370
107
  }
371
- ];
372
- const videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
373
- id: "videoSelfieRecording",
374
- initial: "idle",
375
- context: ({ input }) => {
376
- const visibilityObserver = input.visibilityObserver;
377
- const compositor = input.dependencies?.createCompositor?.() ?? createRecordingCompositor();
378
- return {
379
- service: input.dependencies?.createRecordingService?.() ?? new MultipartRecordingService("videoselfie", new WorkerEncoderRecordingProvider(compositor)),
380
- compositor,
381
- acquireCamera: input.dependencies?.acquireCamera ?? defaultAcquireCamera,
382
- ensureWasmReady: input.ensureWasmReady,
383
- detect: input.dependencies?.detect ?? manualStepDetector,
384
- timer: input.dependencies?.timer ?? BrowserTimerProvider.getInstance(),
385
- visibilityObserver,
386
- isPageVisible: visibilityObserver.isVisible(),
387
- resumeState: "recording",
388
- steps: input.steps && input.steps.length > 0 ? input.steps : ["selfie"],
389
- stepIndex: 0,
390
- stream: void 0,
391
- composite: void 0,
392
- detectionStatus: void 0,
393
- orientation: void 0,
394
- detectionError: void 0,
395
- validationFailure: void 0,
396
- attemptsPerStep: normalizeAttempts(input.attemptsPerStep),
397
- attemptsRemaining: normalizeAttempts(input.attemptsPerStep),
398
- maxVideoLengthSeconds: normalizeMaxVideoLength(input.maxVideoLengthSeconds),
399
- recordingId: null,
400
- error: void 0,
401
- errorKind: void 0,
402
- switchingCameraMinDwellMs: input.dependencies?.switchingCameraMinDwellMs ?? SWITCHING_CAMERA_MIN_DWELL_MS,
403
- cameraSwitchRetried: false
108
+ /**
109
+ * Begins recording media.
110
+ *
111
+ * @param {number} [timeslice] The milliseconds to record into each `Blob`.
112
+ * If this parameter isn’t included, single `Blob`
113
+ * will be recorded.
114
+ *
115
+ * @return {undefined}
116
+ *
117
+ * @example
118
+ * recordButton.addEventListener('click', () => {
119
+ * recorder.start()
120
+ * })
121
+ */
122
+ start(timeslice) {
123
+ if (this.state !== "inactive") return this.em.dispatchEvent(error("start"));
124
+ this.state = "recording";
125
+ if (!context) context = new AudioContext(this.config);
126
+ this.clone = this.stream.clone();
127
+ this.input = context.createMediaStreamSource(this.clone);
128
+ this.processor = context.createScriptProcessor(2048, 1, 1);
129
+ this.encoder.postMessage(["init", context.sampleRate]);
130
+ this.processor.onaudioprocess = (e) => {
131
+ if (this.state === "recording") this.encoder.postMessage(["encode", e.inputBuffer.getChannelData(0)]);
404
132
  };
405
- },
406
- invoke: [{
407
- id: "executionTimer",
408
- src: "executionTimer",
409
- input: ({ context }) => ({ timer: context.timer })
410
- }, {
411
- id: "visibilityChanges",
412
- src: "visibilityChanges",
413
- input: ({ context }) => ({ visibilityObserver: context.visibilityObserver })
414
- }],
415
- on: {
416
- ABORT: { target: ".aborted" },
417
- BACKGROUND: { actions: "markPageHidden" },
418
- FOREGROUND: { actions: "markPageVisible" }
419
- },
420
- states: {
421
- idle: { on: { START: [{
422
- target: "preparingWasm",
423
- guard: "shouldEnterWasmGate"
424
- }, { target: "acquiringCamera" }] } },
425
- preparingWasm: { invoke: {
426
- src: "prepareWasm",
427
- input: ({ context }) => ({ ensure: context.ensureWasmReady }),
428
- onDone: { target: "acquiringCamera" },
429
- onError: {
430
- target: "error",
431
- actions: "setTechnicalActorError"
432
- }
433
- } },
434
- acquiringCamera: { invoke: {
435
- src: "acquireCamera",
436
- input: ({ context }) => ({
437
- acquire: context.acquireCamera,
438
- facingMode: facingModeForStep(context.steps[context.stepIndex]),
439
- withAudio: true
440
- }),
441
- onDone: {
442
- target: "starting",
443
- actions: [({ context, event }) => {
444
- context.compositor.start(eventOutputStream(event));
445
- }, assign({
446
- stream: ({ event }) => eventOutputStream(event),
447
- composite: ({ context }) => context.compositor.stream
448
- })]
449
- },
450
- onError: {
451
- target: "error",
452
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
453
- }
454
- } },
455
- starting: { invoke: {
456
- src: "startRecording",
457
- input: ({ context }) => ({
458
- service: context.service,
459
- stream: context.composite
460
- }),
461
- onDone: [{
462
- target: "pausing",
463
- guard: "pageIsHidden",
464
- actions: ["armExecutionTimer", "prepareRecordingSuspension"]
465
- }, {
466
- target: "recording",
467
- actions: "armExecutionTimer"
468
- }],
469
- onError: {
470
- target: "error",
471
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
472
- }
473
- } },
474
- recording: {
475
- entry: "clearDetectionFeedback",
476
- invoke: {
477
- src: "detectStep",
478
- input: ({ context }) => ({
479
- detect: context.detect,
480
- step: context.steps[context.stepIndex],
481
- stream: context.stream
482
- })
483
- },
484
- on: {
485
- BACKGROUND: {
486
- target: "pausing",
487
- actions: "prepareRecordingSuspension"
488
- },
489
- MAX_VIDEO_LENGTH_REACHED: {
490
- target: "finalizing",
491
- actions: "setMaxVideoLengthReached"
492
- },
493
- DETECTION_STATUS: { actions: "setDetectionStatus" },
494
- ORIENTATION: { actions: "setOrientation" },
495
- DETECTION_DONE: ADVANCE_TRANSITIONS,
496
- NEXT_STEP: ADVANCE_TRANSITIONS,
497
- VALIDATION_FAILED: {
498
- target: "validationFeedback",
499
- actions: "setValidationFailure"
500
- },
501
- RETRYABLE_FAILURE: {
502
- target: "validationFeedback",
503
- actions: "setUncountedRetryableFailure"
504
- },
505
- DETECTION_FAILED: {
506
- target: "error",
507
- actions: "setErrorFromDetection"
508
- }
133
+ this.input.connect(this.processor);
134
+ this.processor.connect(context.destination);
135
+ this.em.dispatchEvent(new Event("start"));
136
+ if (timeslice) this.slicing = setInterval(() => {
137
+ if (this.state === "recording") this.requestData();
138
+ }, timeslice);
139
+ }
140
+ /**
141
+ * Stop media capture and raise `dataavailable` event with recorded data.
142
+ *
143
+ * @return {undefined}
144
+ *
145
+ * @example
146
+ * finishButton.addEventListener('click', () => {
147
+ * recorder.stop()
148
+ * })
149
+ */
150
+ stop() {
151
+ if (this.state === "inactive") return this.em.dispatchEvent(error("stop"));
152
+ this.requestData();
153
+ this.state = "inactive";
154
+ this.clone.getTracks().forEach((track) => {
155
+ track.stop();
156
+ });
157
+ this.processor.disconnect();
158
+ this.input.disconnect();
159
+ return clearInterval(this.slicing);
160
+ }
161
+ /**
162
+ * Pauses recording of media streams.
163
+ *
164
+ * @return {undefined}
165
+ *
166
+ * @example
167
+ * pauseButton.addEventListener('click', () => {
168
+ * recorder.pause()
169
+ * })
170
+ */
171
+ pause() {
172
+ if (this.state !== "recording") return this.em.dispatchEvent(error("pause"));
173
+ this.state = "paused";
174
+ return this.em.dispatchEvent(new Event("pause"));
175
+ }
176
+ /**
177
+ * Resumes media recording when it has been previously paused.
178
+ *
179
+ * @return {undefined}
180
+ *
181
+ * @example
182
+ * resumeButton.addEventListener('click', () => {
183
+ * recorder.resume()
184
+ * })
185
+ */
186
+ resume() {
187
+ if (this.state !== "paused") return this.em.dispatchEvent(error("resume"));
188
+ this.state = "recording";
189
+ return this.em.dispatchEvent(new Event("resume"));
190
+ }
191
+ /**
192
+ * Raise a `dataavailable` event containing the captured media.
193
+ *
194
+ * @return {undefined}
195
+ *
196
+ * @example
197
+ * this.on('nextData', () => {
198
+ * recorder.requestData()
199
+ * })
200
+ */
201
+ requestData() {
202
+ if (this.state === "inactive") return this.em.dispatchEvent(error("requestData"));
203
+ return this.encoder.postMessage(["dump", context.sampleRate]);
204
+ }
205
+ /**
206
+ * Add listener for specified event type.
207
+ *
208
+ * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
209
+ * type Event type.
210
+ * @param {function} listener The listener function.
211
+ *
212
+ * @return {undefined}
213
+ *
214
+ * @example
215
+ * recorder.addEventListener('dataavailable', e => {
216
+ * audio.src = URL.createObjectURL(e.data)
217
+ * })
218
+ */
219
+ addEventListener(...args) {
220
+ this.em.addEventListener(...args);
221
+ }
222
+ /**
223
+ * Remove event listener.
224
+ *
225
+ * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
226
+ * type Event type.
227
+ * @param {function} listener The same function used in `addEventListener`.
228
+ *
229
+ * @return {undefined}
230
+ */
231
+ removeEventListener(...args) {
232
+ this.em.removeEventListener(...args);
233
+ }
234
+ /**
235
+ * Calls each of the listeners registered for a given event.
236
+ *
237
+ * @param {Event} event The event object.
238
+ *
239
+ * @return {boolean} Is event was no canceled by any listener.
240
+ */
241
+ dispatchEvent(...args) {
242
+ this.em.dispatchEvent(...args);
243
+ }
244
+ };
245
+ /**
246
+ * The MIME type that is being used for recording.
247
+ * @type {string}
248
+ */
249
+ MediaRecorder.prototype.mimeType = "audio/wav";
250
+ /**
251
+ * Returns `true` if the MIME type specified is one the polyfill can record.
252
+ *
253
+ * This polyfill supports `audio/wav` and `audio/mpeg`.
254
+ *
255
+ * @param {string} mimeType The mimeType to check.
256
+ *
257
+ * @return {boolean} `true` on `audio/wav` and `audio/mpeg` MIME type.
258
+ */
259
+ MediaRecorder.isTypeSupported = (mimeType) => {
260
+ return MediaRecorder.prototype.mimeType === mimeType;
261
+ };
262
+ /**
263
+ * `true` if MediaRecorder can not be polyfilled in the current browser.
264
+ * @type {boolean}
265
+ *
266
+ * @example
267
+ * if (MediaRecorder.notSupported) {
268
+ * showWarning('Audio recording is not supported in this browser')
269
+ * }
270
+ */
271
+ MediaRecorder.notSupported = !navigator.mediaDevices || !AudioContext;
272
+ /**
273
+ * Converts RAW audio buffer to compressed audio files.
274
+ * It will be loaded to Web Worker.
275
+ * By default, WAVE encoder will be used.
276
+ * @type {function}
277
+ *
278
+ * @example
279
+ * MediaRecorder.prototype.mimeType = 'audio/ogg'
280
+ * MediaRecorder.encoder = oggEncoder
281
+ */
282
+ MediaRecorder.encoder = wave_encoder_default;
283
+ //#endregion
284
+ //#region ../infra/src/media/blobEncoding.ts
285
+ /** Encodes a blob as raw base64 bytes (no `data:...;base64,` prefix). */
286
+ function blobToRawBase64(blob) {
287
+ return new Promise((resolve, reject) => {
288
+ const reader = new FileReader();
289
+ reader.onloadend = () => {
290
+ const result = reader.result;
291
+ if (typeof result !== "string") {
292
+ reject(/* @__PURE__ */ new Error("Failed to encode blob as base64"));
293
+ return;
509
294
  }
510
- },
511
- validationFeedback: {
512
- on: {
513
- BACKGROUND: {
514
- target: "pausing",
515
- actions: ["markPageHidden", assign({ resumeState: () => "validationFeedback" })]
516
- },
517
- MAX_VIDEO_LENGTH_REACHED: {
518
- target: "finalizing",
519
- actions: "setMaxVideoLengthReached"
520
- }
521
- },
522
- after: { VALIDATION_FEEDBACK_DWELL: [{
523
- target: "recording",
524
- guard: "hasAttemptsRemaining"
525
- }, {
526
- target: "finalizing",
527
- actions: "setValidationAttemptsExhausted"
528
- }] }
529
- },
530
- flippingId: {
531
- after: { ID_FLIP_DWELL: {
532
- target: "recording",
533
- actions: "advanceStep"
534
- } },
535
- on: {
536
- BACKGROUND: {
537
- target: "pausing",
538
- actions: ["markPageHidden", assign({ resumeState: () => "flippingId" })]
539
- },
540
- MAX_VIDEO_LENGTH_REACHED: {
541
- target: "finalizing",
542
- actions: "setMaxVideoLengthReached"
543
- },
544
- NEXT_STEP: {
545
- target: "recording",
546
- actions: "advanceStep"
547
- }
548
- }
549
- },
550
- switchingCamera: {
551
- on: {
552
- BACKGROUND: {
553
- target: "pausing",
554
- actions: ["markPageHidden", assign({ resumeState: () => "switchingCamera" })]
555
- },
556
- MAX_VIDEO_LENGTH_REACHED: {
557
- target: "finalizing",
558
- actions: "setMaxVideoLengthReached"
559
- }
560
- },
561
- invoke: {
562
- src: "switchCamera",
563
- input: ({ context }) => ({
564
- acquire: context.acquireCamera,
565
- facingMode: facingModeForStep(context.steps[context.stepIndex]),
566
- minDwellMs: context.switchingCameraMinDwellMs
567
- }),
568
- onDone: {
569
- target: "recording",
570
- actions: [({ context, event }) => {
571
- context.compositor.switchVideo(eventOutputStream(event));
572
- }, assign({ stream: ({ event }) => eventOutputStream(event) })]
573
- },
574
- onError: [{
575
- target: "releasingCamera",
576
- guard: "canRetryCameraSwitchAfterRelease"
577
- }, {
578
- target: "error",
579
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
580
- }]
581
- }
582
- },
583
- releasingCamera: {
584
- entry: ["releaseCurrentVideo", assign({ cameraSwitchRetried: () => true })],
585
- on: {
586
- BACKGROUND: {
587
- target: "pausing",
588
- actions: ["markPageHidden", assign({ resumeState: () => "switchingCamera" })]
589
- },
590
- MAX_VIDEO_LENGTH_REACHED: {
591
- target: "finalizing",
592
- actions: "setMaxVideoLengthReached"
593
- }
594
- },
595
- after: { CAMERA_RELEASE_SETTLE: { target: "switchingCamera" } }
596
- },
597
- pausing: {
598
- on: { MAX_VIDEO_LENGTH_REACHED: { actions: "setMaxVideoLengthReached" } },
599
- invoke: {
600
- src: "pauseCapture",
601
- input: ({ context }) => ({
602
- service: context.service,
603
- compositor: context.compositor
604
- }),
605
- onDone: [
606
- {
607
- target: "finalizing",
608
- guard: "maxVideoLengthWasReached"
609
- },
610
- {
611
- target: "error",
612
- guard: "pauseFailed",
613
- actions: "setPauseError"
614
- },
615
- {
616
- target: "resumingCompositor",
617
- guard: "pauseSucceededWhileVisible"
618
- },
619
- { target: "suspended" }
620
- ],
621
- onError: [{
622
- target: "finalizing",
623
- guard: "maxVideoLengthWasReached"
624
- }, {
625
- target: "error",
626
- actions: "setTechnicalActorError"
627
- }]
628
- }
629
- },
630
- suspended: { on: {
631
- FOREGROUND: {
632
- target: "resumingCompositor",
633
- actions: "markPageVisible"
634
- },
635
- MAX_VIDEO_LENGTH_REACHED: {
636
- target: "finalizing",
637
- actions: "setMaxVideoLengthReached"
638
- }
639
- } },
640
- resumingCompositor: {
641
- on: {
642
- BACKGROUND: {
643
- target: "pausing",
644
- actions: "markPageHidden"
645
- },
646
- MAX_VIDEO_LENGTH_REACHED: {
647
- target: "finalizing",
648
- actions: "setMaxVideoLengthReached"
649
- }
650
- },
651
- invoke: {
652
- src: "resumeCompositor",
653
- input: ({ context }) => ({ compositor: context.compositor }),
654
- onDone: [
655
- {
656
- target: "error",
657
- guard: "compositorAudioEnded",
658
- actions: "setAudioEndedError"
659
- },
660
- {
661
- target: "reacquiringCamera",
662
- guard: "forceVideoRecovery"
663
- },
664
- {
665
- target: "reacquiringCamera",
666
- guard: "compositorDidNotResume"
667
- },
668
- { target: "resumingRecorder" }
669
- ],
670
- onError: {
671
- target: "error",
672
- actions: "setTechnicalActorError"
673
- }
674
- }
675
- },
676
- reacquiringCamera: {
677
- on: {
678
- BACKGROUND: {
679
- target: "pausing",
680
- actions: "markPageHidden"
681
- },
682
- MAX_VIDEO_LENGTH_REACHED: {
683
- target: "finalizing",
684
- actions: "setMaxVideoLengthReached"
685
- }
686
- },
687
- invoke: {
688
- src: "acquireCamera",
689
- input: ({ context }) => ({
690
- acquire: context.acquireCamera,
691
- facingMode: facingModeForStep(context.steps[context.stepIndex]),
692
- withAudio: false
693
- }),
694
- onDone: {
695
- target: "validatingRecoveredCamera",
696
- actions: [({ context, event }) => {
697
- context.compositor.switchVideo(eventOutputStream(event));
698
- }, assign({ stream: ({ event }) => eventOutputStream(event) })]
699
- },
700
- onError: {
701
- target: "error",
702
- actions: "setTechnicalActorError"
703
- }
295
+ const commaIndex = result.indexOf(",");
296
+ resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result);
297
+ };
298
+ reader.onerror = () => {
299
+ reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read blob"));
300
+ };
301
+ reader.readAsDataURL(blob);
302
+ });
303
+ }
304
+ //#endregion
305
+ //#region ../infra/src/providers/browser/mp3Encoder.ts
306
+ /**
307
+ * Worker body of `audio-recorder-polyfill/mpeg-encoder` (MIT), without its
308
+ * `importScripts()` of lamejs from cdnjs. Same message protocol:
309
+ * `['init', sampleRate]`, `['encode', Float32Array]`, `['dump']`.
310
+ */
311
+ const ENCODER_SOURCE = `
312
+ var CHANNELS = 1;
313
+ var KBPS = 128;
314
+ var DEFAULT_SAMPLE_RATE = 44100;
315
+ var encoder;
316
+ var recorded = new Int8Array();
317
+
318
+ function concat(a, b) {
319
+ if (b.length === 0) return a;
320
+ var c = new Int8Array(a.length + b.length);
321
+ c.set(a);
322
+ c.set(b, a.length);
323
+ return c;
324
+ }
325
+
326
+ onmessage = function (e) {
327
+ if (e.data[0] === 'init') {
328
+ encoder = new lamejs.Mp3Encoder(CHANNELS, e.data[1] || DEFAULT_SAMPLE_RATE, KBPS);
329
+ } else if (e.data[0] === 'encode') {
330
+ var buffer = e.data[1];
331
+ for (var i = 0; i < buffer.length; i++) buffer[i] = buffer[i] * 32767.5;
332
+ recorded = concat(recorded, encoder.encodeBuffer(buffer));
333
+ } else {
334
+ recorded = concat(recorded, encoder.flush());
335
+ var out = recorded.buffer;
336
+ recorded = new Int8Array();
337
+ postMessage(out, [out]);
338
+ }
339
+ };
340
+ `;
341
+ /**
342
+ * MP3 encoder for `audio-recorder-polyfill` that runs the given lamejs source
343
+ * inside the worker, so voice consent makes no third-party request. The
344
+ * polyfill runs an encoder by stringifying it into a blob: worker
345
+ * (`encoder.toString()`, minus the `() => {` wrapper), so only `toString()`
346
+ * matters here.
347
+ */
348
+ function createMp3Encoder(lameSource) {
349
+ return { toString: () => `() => {\n${lameSource}\n${ENCODER_SOURCE}\n}` };
350
+ }
351
+ //#endregion
352
+ //#region ../infra/src/providers/browser/VoiceRecordingProvider.ts
353
+ const AudioRecorderPolyfill = MediaRecorder;
354
+ AudioRecorderPolyfill.prototype.mimeType = "audio/mpeg";
355
+ const MIME_TYPE = "audio/mpeg";
356
+ const lamejs = BrowserLazyImportProvider.getInstance().create(() => import("./lamejs-B6TzhU5q.esm.js").then((module) => module.default), {
357
+ label: "Voice consent",
358
+ moduleName: "lamejs",
359
+ timeoutMs: 1e4,
360
+ disabledMessage: "voice consent cannot record audio."
361
+ });
362
+ /**
363
+ * Records the audio of a stream and produces a single MP3 blob. Used by the
364
+ * Video Selfie voice-consent step to capture the spoken consent phrase
365
+ * independently of the main video recording (which keeps running on its own
366
+ * recorder).
367
+ *
368
+ * Records from a clone of the supplied stream so stopping this recorder never
369
+ * tears down tracks the main recording is still using.
370
+ *
371
+ * The MP3 encoder (lamejs) loads lazily. Flows with voice consent call
372
+ * {@link VoiceRecordingProvider.preload} when they begin, and construction
373
+ * loads it too, so it is ready long before the user speaks. If it is not,
374
+ * `start()` begins recording once it has loaded.
375
+ */
376
+ var VoiceRecordingProvider = class VoiceRecordingProvider {
377
+ /** Starts loading the MP3 encoder, e.g. when a flow with voice consent begins. */
378
+ static preload() {
379
+ lamejs.load();
380
+ }
381
+ constructor() {
382
+ this.recorder = null;
383
+ this.chunks = [];
384
+ this._isRecording = false;
385
+ this.pendingStart = null;
386
+ this.startGeneration = 0;
387
+ VoiceRecordingProvider.preload();
388
+ }
389
+ get isRecording() {
390
+ return this._isRecording;
391
+ }
392
+ start(stream) {
393
+ this.reset();
394
+ this.chunks = [];
395
+ const lameSource = lamejs.get();
396
+ if (lameSource !== void 0) {
397
+ this.startRecorder(stream, lameSource);
398
+ this._isRecording = true;
399
+ return;
400
+ }
401
+ this._isRecording = true;
402
+ const generation = this.startGeneration;
403
+ this.pendingStart = lamejs.load().then((source) => {
404
+ if (source !== void 0 && generation === this.startGeneration) this.startRecorder(stream, source);
405
+ }).catch(() => {});
406
+ }
407
+ startRecorder(stream, lameSource) {
408
+ AudioRecorderPolyfill.encoder = createMp3Encoder(lameSource);
409
+ const audioOnly = new MediaStream(stream.getAudioTracks());
410
+ const recorder = new AudioRecorderPolyfill(audioOnly.clone());
411
+ recorder.mimeType = MIME_TYPE;
412
+ recorder.addEventListener("dataavailable", (event) => {
413
+ if (event.data.size > 0) this.chunks.push(event.data);
414
+ });
415
+ recorder.start();
416
+ this.recorder = recorder;
417
+ }
418
+ async stop() {
419
+ const pending = this.pendingStart;
420
+ await pending;
421
+ if (this.pendingStart === pending) this.pendingStart = null;
422
+ const recorder = this.recorder;
423
+ return new Promise((resolve) => {
424
+ if (!recorder || !this._isRecording) {
425
+ this._isRecording = false;
426
+ resolve(new Blob([], { type: MIME_TYPE }));
427
+ return;
704
428
  }
705
- },
706
- validatingRecoveredCamera: {
707
- on: {
708
- BACKGROUND: {
709
- target: "pausing",
710
- actions: "markPageHidden"
711
- },
712
- MAX_VIDEO_LENGTH_REACHED: {
713
- target: "finalizing",
714
- actions: "setMaxVideoLengthReached"
715
- }
716
- },
717
- invoke: {
718
- src: "resumeCompositor",
719
- input: ({ context }) => ({ compositor: context.compositor }),
720
- onDone: [
721
- {
722
- target: "error",
723
- guard: "compositorAudioEnded",
724
- actions: "setAudioEndedError"
725
- },
726
- {
727
- target: "error",
728
- guard: "compositorDidNotResume",
729
- actions: "setCameraResumeError"
730
- },
731
- { target: "resumingRecorder" }
732
- ],
733
- onError: {
734
- target: "error",
735
- actions: "setTechnicalActorError"
736
- }
737
- }
738
- },
739
- resumingRecorder: {
740
- on: {
741
- BACKGROUND: {
742
- target: "pausing",
743
- actions: "markPageHidden"
744
- },
745
- MAX_VIDEO_LENGTH_REACHED: {
746
- target: "finalizing",
747
- actions: "setMaxVideoLengthReached"
748
- }
749
- },
750
- invoke: {
751
- src: "resumeRecorder",
752
- input: ({ context }) => ({ service: context.service }),
753
- onDone: [
754
- {
755
- target: "error",
756
- guard: "recorderResumeFailed",
757
- actions: "setRecorderResumeError"
758
- },
759
- {
760
- target: "validationFeedback",
761
- guard: "resumeValidationFeedback"
762
- },
763
- {
764
- target: "flippingId",
765
- guard: "resumeFlippingId"
766
- },
767
- { target: "recording" }
768
- ],
769
- onError: {
770
- target: "error",
771
- actions: "setTechnicalActorError"
772
- }
773
- }
774
- },
775
- finalizing: {
776
- entry: "cancelExecutionTimer",
777
- invoke: {
778
- src: "finalizeRecording",
779
- input: ({ context }) => ({ service: context.service }),
780
- onDone: [
781
- {
782
- target: "error",
783
- guard: "maxVideoLengthWasReached",
784
- actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
785
- },
786
- {
787
- target: "error",
788
- guard: "validationAttemptsWereExhausted",
789
- actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
790
- },
791
- {
792
- target: "error",
793
- guard: "uploadReturnedNoRecordingId",
794
- actions: [assign({ recordingId: ({ event }) => recordingIdFromEvent(event) }), "setUploadFailedError"]
795
- },
796
- {
797
- target: "success",
798
- actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
799
- }
800
- ],
801
- onError: [
802
- {
803
- target: "error",
804
- guard: "maxVideoLengthWasReached"
805
- },
806
- {
807
- target: "error",
808
- guard: "validationAttemptsWereExhausted"
809
- },
810
- {
811
- target: "error",
812
- actions: assign({ error: ({ event }) => errorFromEvent(event) })
813
- }
814
- ]
815
- }
816
- },
817
- success: {
818
- entry: ["cancelExecutionTimer", "cleanup"],
819
- after: { FINAL_SUCCESS_DWELL: "finished" }
820
- },
821
- finished: { type: "final" },
822
- error: {
823
- entry: ["cancelExecutionTimer", "cleanup"],
824
- after: { MAX_VIDEO_LENGTH_ERROR_DWELL: {
825
- target: "finished",
826
- guard: "maxVideoLengthWasReached"
827
- } },
828
- on: { RETRY: [{
829
- target: "preparingWasm",
830
- guard: "shouldEnterWasmGate",
831
- actions: "resetSession"
832
- }, {
833
- target: "acquiringCamera",
834
- guard: "errorIsRetryable",
835
- actions: "resetSession"
836
- }] }
837
- },
838
- aborted: {
839
- type: "final",
840
- entry: ["cancelExecutionTimer", "cleanup"]
429
+ recorder.addEventListener("stop", () => {
430
+ this._isRecording = false;
431
+ recorder.stream?.getTracks().forEach((track) => track.stop());
432
+ this.recorder = null;
433
+ resolve(new Blob(this.chunks, { type: MIME_TYPE }));
434
+ });
435
+ recorder.stop();
436
+ });
437
+ }
438
+ async stopAsBase64() {
439
+ const blob = await this.stop();
440
+ if (blob.size === 0) return "";
441
+ return blobToRawBase64(blob);
442
+ }
443
+ reset() {
444
+ this.startGeneration += 1;
445
+ this.pendingStart = null;
446
+ const recorder = this.recorder;
447
+ if (recorder && this._isRecording) {
448
+ try {
449
+ recorder.stop();
450
+ } catch {}
451
+ recorder.stream?.getTracks().forEach((track) => track.stop());
841
452
  }
453
+ this.recorder = null;
454
+ this._isRecording = false;
842
455
  }
843
- });
456
+ };
844
457
  //#endregion
845
- //#region src/modules/video-selfie/videoSelfieRecordingActor.ts
846
- function getRecordingSteps(options) {
847
- if (options.steps && options.steps.length > 0) return options.steps;
848
- if (options.config) return resolveVideoSelfieSteps(options.config);
849
- }
850
- function createRetryablePreparation(prepare) {
851
- let readiness;
852
- return () => {
853
- if (!readiness) {
854
- readiness = Promise.resolve().then(prepare).catch((error) => {
855
- readiness = void 0;
856
- throw error;
857
- });
858
- readiness.catch(() => void 0);
859
- }
860
- return readiness;
861
- };
862
- }
863
- function createVideoSelfieRecordingActor(visibilityObserver, options = {}) {
864
- const config = options.config;
865
- const steps = getRecordingSteps(options);
866
- const detect = options.dependencies?.detect ?? (config ? createDefaultStepDetector(config, { getWasmUtil: () => WasmUtilProvider.getInstance() }) : void 0);
867
- const prepareWasm = config && steps ? () => warmupVideoSelfieWasm(config, steps) : void 0;
868
- const ensureWasmReady = prepareWasm ? createRetryablePreparation(prepareWasm) : void 0;
869
- if (ensureWasmReady) ensureWasmReady();
870
- return createActor(videoSelfieRecordingMachine, { input: {
871
- steps,
872
- attemptsPerStep: config?.numberOfTries,
873
- maxVideoLengthSeconds: config?.maxWaitingVideoSelfieFile,
874
- visibilityObserver,
875
- ensureWasmReady,
876
- dependencies: {
877
- ...options.dependencies,
878
- detect
879
- }
880
- } }).start();
458
+ //#region src/modules/video-selfie/videoSelfieWasm.ts
459
+ function warmupVideoSelfieWasm(config, steps = resolveVideoSelfieSteps(config)) {
460
+ return ensureWasmPipelines(videoSelfieWasmPipelines(config, steps));
881
461
  }
882
462
  //#endregion
883
- //#region src/modules/video-selfie/videoSelfieRecordingManager.ts
884
- function mapState(snapshot) {
885
- const value = typeof snapshot.value === "string" ? snapshot.value : "idle";
886
- const { context } = snapshot;
887
- const status = value === "preparingWasm" || value === "acquiringCamera" ? "starting" : value === "pausing" || value === "suspended" || value === "resumingCompositor" || value === "reacquiringCamera" || value === "validatingRecoveredCamera" || value === "resumingRecorder" ? "recording" : value === "releasingCamera" ? "switchingCamera" : value;
888
- const steps = context.steps;
889
- const stepIndex = context.stepIndex;
890
- return {
891
- status,
892
- step: steps[stepIndex],
893
- stepIndex,
894
- isLastStep: stepIndex >= steps.length - 1,
895
- stream: context.stream,
896
- composite: context.composite,
897
- detectionStatus: context.detectionStatus,
898
- orientation: context.orientation,
899
- validationFailure: context.validationFailure,
900
- attemptsRemaining: context.attemptsRemaining,
901
- recordingId: context.recordingId,
902
- error: context.error,
903
- errorKind: context.errorKind
904
- };
463
+ //#region src/modules/video-selfie/videoSelfieRecordingMachine.ts
464
+ /** A detector that never settles — used when a step has no auto-capture yet. */
465
+ const manualStepDetector = () => new Promise(() => {});
466
+ function eventOutputStream(event) {
467
+ return event.output;
905
468
  }
906
- function createApi({ actor }) {
907
- return {
908
- /** Acquire the first camera, open the upload, and begin recording. */
909
- start() {
910
- actor.send({ type: "START" });
911
- },
912
- retry() {
913
- actor.send({ type: "RETRY" });
914
- },
915
- /**
916
- * Advance to the next step. Switches the camera when the next step's
917
- * facing mode differs; finalizes the upload on the last step.
918
- */
919
- nextStep() {
920
- actor.send({ type: "NEXT_STEP" });
921
- },
922
- /** Abandon the session without finalizing the upload. */
923
- abort() {
924
- actor.send({ type: "ABORT" });
925
- }
926
- };
469
+ function recordingIdFromEvent(event) {
470
+ return event.output.recordingId;
471
+ }
472
+ function errorFromEvent(event) {
473
+ return String(event.error);
927
474
  }
928
475
  /**
929
- * Creates a manager for a multi-step, camera-switching Video Selfie recording.
930
- *
931
- * The whole session records as one continuous video that follows the camera
932
- * across steps (front for selfie, rear for ID). Exposes `getState()` /
933
- * `subscribe()` (status, step, preview `stream`, `recordingId`) plus `start()`,
934
- * `nextStep()`, and `abort()`.
476
+ * Chromium reports a camera the device cannot open right now as
477
+ * `NotReadableError` ("Could not start video source") or `AbortError`
478
+ * ("Starting videoinput failed") — the same pair the rear-camera acquire
479
+ * retries on. Anything else (permission, no device) will not clear by
480
+ * releasing our own camera.
935
481
  */
936
- function createVideoSelfieRecordingManager(options = {}) {
937
- const actor = createVideoSelfieRecordingActor(options.dependencies?.visibilityObserver ?? new BrowserVisibilityObserverProvider(), options);
938
- const manager = createManager({
939
- actor,
940
- mapState,
941
- createApi
942
- });
943
- return {
944
- ...manager,
945
- stop() {
946
- if (actor.getSnapshot().status === "active") actor.send({ type: "ABORT" });
947
- manager.stop();
948
- }
949
- };
482
+ function isCameraBusyError(error) {
483
+ const name = typeof error === "object" && error !== null && "name" in error ? error.name : void 0;
484
+ return name === "NotReadableError" || name === "AbortError";
950
485
  }
951
- //#endregion
952
- //#region src/modules/video-selfie/videoSelfieSpeechService.ts
953
- /**
954
- * Fetches `count` random challenge questions the user answers out loud. The
955
- * spoken answers are captured in the main video recording (no separate upload)
956
- * and verified server-side.
957
- */
958
- async function getVideoSelfieQuestions(count) {
959
- return (await api.get(endpoints.questionAndAnswer, { query: {
960
- numberOfQuestions: count,
961
- returnVoiceConsentQuestion: false
962
- } })).data.questionAndAnswers ?? [];
486
+ function detectionStatusFromEvent(event) {
487
+ return event.status;
963
488
  }
964
- /**
965
- * Fetches the voice-consent phrase the user must read aloud. The phrase is the
966
- * `answer` of the consent question returned when `returnVoiceConsentQuestion`
967
- * is requested with no challenge questions.
968
- */
969
- async function getVoiceConsentPhrase() {
970
- return (await api.get(endpoints.questionAndAnswer, { query: {
971
- numberOfQuestions: 0,
972
- returnVoiceConsentQuestion: true
973
- } })).data.questionAndAnswers?.[0]?.answer ?? "";
489
+ function detectionErrorFromEvent(event) {
490
+ return event.error;
491
+ }
492
+ function validationFailureFromEvent(event) {
493
+ return event.failure;
494
+ }
495
+ function errorKindFromEvent(event) {
496
+ return event.kind;
974
497
  }
498
+ function retryableFailureFromEvent(event) {
499
+ return event.failure;
500
+ }
501
+ function orientationFromEvent(event) {
502
+ return event.orientation;
503
+ }
504
+ const SWITCHING_CAMERA_MIN_DWELL_MS = 3e3;
975
505
  /**
976
- * Uploads the recorded consent audio (base64 MP3) for speech-to-text
977
- * validation. Resolves `{ success: false }` on a network/validation error so
978
- * the UI can drive its retry loop instead of throwing.
506
+ * How long a just-stopped camera gets to release before another one is
507
+ * opened (see `releasingCamera`). Same settle the rear-camera acquire uses
508
+ * on a busy device; a hardware knob, not a protocol value.
979
509
  */
980
- async function addSpeech(base64Mp3) {
981
- try {
982
- return { success: (await api.post(endpoints.addSpeechV2, {
983
- base64Media: base64Mp3,
984
- audioOnly: true
985
- })).data.success === true };
986
- } catch {
987
- return { success: false };
988
- }
510
+ const CAMERA_RELEASE_SETTLE_MS = 300;
511
+ const ID_FLIP_DWELL_MS = 4e3;
512
+ const FINAL_SUCCESS_DWELL_MS = 3e3;
513
+ const MAX_VIDEO_LENGTH_ERROR_DWELL_MS = 3e3;
514
+ const VALIDATION_FEEDBACK_DWELL_MS = 3e3;
515
+ const DEFAULT_ATTEMPTS_PER_STEP = 3;
516
+ const DEFAULT_MAX_VIDEO_LENGTH_SECONDS = 300;
517
+ const UNLIMITED_ATTEMPTS = -1;
518
+ function normalizeAttempts(attempts) {
519
+ if (attempts === UNLIMITED_ATTEMPTS) return UNLIMITED_ATTEMPTS;
520
+ if (typeof attempts === "number" && attempts > 0) return attempts;
521
+ return DEFAULT_ATTEMPTS_PER_STEP;
522
+ }
523
+ function normalizeMaxVideoLength(seconds) {
524
+ if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0) return seconds;
525
+ return DEFAULT_MAX_VIDEO_LENGTH_SECONDS;
526
+ }
527
+ function waitForDwell(ms, signal) {
528
+ const timerProvider = BrowserTimerProvider.getInstance();
529
+ return new Promise((resolve) => {
530
+ const onAbort = () => {
531
+ timerProvider.clearTimeout(timer);
532
+ resolve(false);
533
+ };
534
+ const timer = timerProvider.setTimeout(() => {
535
+ signal.removeEventListener("abort", onAbort);
536
+ resolve(true);
537
+ }, ms);
538
+ signal.addEventListener("abort", onAbort, { once: true });
539
+ if (signal.aborted) onAbort();
540
+ });
989
541
  }
990
- //#endregion
991
- //#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/wave-encoder/index.js
992
- var wave_encoder_default = () => {
993
- let BYTES_PER_SAMPLE = 2;
994
- let recorded = [];
995
- function encode(buffer) {
996
- let length = buffer.length;
997
- let data = new Uint8Array(length * BYTES_PER_SAMPLE);
998
- for (let i = 0; i < length; i++) {
999
- let index = i * BYTES_PER_SAMPLE;
1000
- let sample = buffer[i];
1001
- if (sample > 1) sample = 1;
1002
- else if (sample < -1) sample = -1;
1003
- sample = sample * 32768;
1004
- data[index] = sample;
1005
- data[index + 1] = sample >> 8;
1006
- }
1007
- recorded.push(data);
1008
- }
1009
- function dump(sampleRate) {
1010
- let bufferLength = recorded.length ? recorded[0].length : 0;
1011
- let length = recorded.length * bufferLength;
1012
- let wav = new Uint8Array(44 + length);
1013
- let view = new DataView(wav.buffer);
1014
- view.setUint32(0, 1380533830, false);
1015
- view.setUint32(4, 36 + length, true);
1016
- view.setUint32(8, 1463899717, false);
1017
- view.setUint32(12, 1718449184, false);
1018
- view.setUint32(16, 16, true);
1019
- view.setUint16(20, 1, true);
1020
- view.setUint16(22, 1, true);
1021
- view.setUint32(24, sampleRate, true);
1022
- view.setUint32(28, sampleRate * BYTES_PER_SAMPLE, true);
1023
- view.setUint16(32, BYTES_PER_SAMPLE, true);
1024
- view.setUint16(34, 8 * BYTES_PER_SAMPLE, true);
1025
- view.setUint32(36, 1684108385, false);
1026
- view.setUint32(40, length, true);
1027
- for (let i = 0; i < recorded.length; i++) wav.set(recorded[i], i * bufferLength + 44);
1028
- recorded = [];
1029
- postMessage(wav.buffer, [wav.buffer]);
1030
- }
1031
- onmessage = (e) => {
1032
- if (e.data[0] === "encode") encode(e.data[1]);
1033
- else if (e.data[0] === "dump") dump(e.data[1]);
1034
- };
1035
- };
1036
- //#endregion
1037
- //#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/index.js
1038
- let AudioContext = window.AudioContext || window.webkitAudioContext;
1039
- let createWorker = (fn) => {
1040
- let js = fn.toString().replace(/^(\(\)\s*=>|function\s*\(\))\s*{/, "").replace(/}$/, "");
1041
- let blob = new Blob([js]);
1042
- return new Worker(URL.createObjectURL(blob));
1043
- };
1044
- let error = (method) => {
1045
- let event = new Event("error");
1046
- event.data = /* @__PURE__ */ new Error("Wrong state for " + method);
1047
- return event;
1048
- };
1049
- let context;
1050
542
  /**
1051
- * Audio Recorder with MediaRecorder API.
543
+ * Orchestrates a multi-step Video Selfie session as ONE continuous recording
544
+ * that follows the camera between steps AND auto-advances per step:
1052
545
  *
1053
- * @example
1054
- * navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
1055
- * let recorder = new MediaRecorder(stream)
1056
- * })
546
+ * idle ─START▶ [preparingWasm ─▶] acquiringCamera ─▶ starting
547
+ * │
548
+ * ▼
549
+ * recording ⇄ switchingCamera
550
+ * │ │ busy camera (ENG-57332)
551
+ * │ ▼
552
+ * │ releasingCamera ─settle▶ retry once
553
+ * │ (capture validated → DETECTION_DONE,
554
+ * │ or manual NEXT_STEP)
555
+ * last step ─▶ finalizing ─▶ finished
556
+ *
557
+ * While `recording`, a per-step detector runs WASM auto-capture + backend
558
+ * validation; success advances the step (switching the camera when the next
559
+ * step's facing differs), and the final step finalizes the multipart upload.
560
+ *
561
+ * Teardown: `ABORT` is accepted from ANY non-final state and every settled
562
+ * path runs `cleanup`, so no path leaves camera or mic tracks running.
563
+ * XState v5 executes NO exit actions on an external
564
+ * `actor.stop()`, so the manager sends `ABORT` before stopping the actor —
565
+ * never stop this actor directly without aborting first.
1057
566
  */
1058
- var MediaRecorder = class MediaRecorder {
1059
- /**
1060
- * @param {MediaStream} stream The audio stream to record.
1061
- */
1062
- constructor(stream, config = null) {
1063
- /**
1064
- * The `MediaStream` passed into the constructor.
1065
- * @type {MediaStream}
1066
- */
1067
- this.stream = stream;
1068
- this.config = config;
1069
- /**
1070
- * The current state of recording process.
1071
- * @type {"inactive"|"recording"|"paused"}
1072
- */
1073
- this.state = "inactive";
1074
- this.em = document.createDocumentFragment();
1075
- this.encoder = createWorker(MediaRecorder.encoder);
1076
- let recorder = this;
1077
- this.encoder.addEventListener("message", (e) => {
1078
- let event = new Event("dataavailable");
1079
- event.data = new Blob([e.data], { type: recorder.mimeType });
1080
- recorder.em.dispatchEvent(event);
1081
- if (recorder.state === "inactive") recorder.em.dispatchEvent(new Event("stop"));
1082
- });
1083
- }
1084
- /**
1085
- * Begins recording media.
1086
- *
1087
- * @param {number} [timeslice] The milliseconds to record into each `Blob`.
1088
- * If this parameter isn’t included, single `Blob`
1089
- * will be recorded.
1090
- *
1091
- * @return {undefined}
1092
- *
1093
- * @example
1094
- * recordButton.addEventListener('click', () => {
1095
- * recorder.start()
1096
- * })
1097
- */
1098
- start(timeslice) {
1099
- if (this.state !== "inactive") return this.em.dispatchEvent(error("start"));
1100
- this.state = "recording";
1101
- if (!context) context = new AudioContext(this.config);
1102
- this.clone = this.stream.clone();
1103
- this.input = context.createMediaStreamSource(this.clone);
1104
- this.processor = context.createScriptProcessor(2048, 1, 1);
1105
- this.encoder.postMessage(["init", context.sampleRate]);
1106
- this.processor.onaudioprocess = (e) => {
1107
- if (this.state === "recording") this.encoder.postMessage(["encode", e.inputBuffer.getChannelData(0)]);
1108
- };
1109
- this.input.connect(this.processor);
1110
- this.processor.connect(context.destination);
1111
- this.em.dispatchEvent(new Event("start"));
1112
- if (timeslice) this.slicing = setInterval(() => {
1113
- if (this.state === "recording") this.requestData();
1114
- }, timeslice);
1115
- }
1116
- /**
1117
- * Stop media capture and raise `dataavailable` event with recorded data.
1118
- *
1119
- * @return {undefined}
1120
- *
1121
- * @example
1122
- * finishButton.addEventListener('click', () => {
1123
- * recorder.stop()
1124
- * })
1125
- */
1126
- stop() {
1127
- if (this.state === "inactive") return this.em.dispatchEvent(error("stop"));
1128
- this.requestData();
1129
- this.state = "inactive";
1130
- this.clone.getTracks().forEach((track) => {
1131
- track.stop();
1132
- });
1133
- this.processor.disconnect();
1134
- this.input.disconnect();
1135
- return clearInterval(this.slicing);
1136
- }
1137
- /**
1138
- * Pauses recording of media streams.
1139
- *
1140
- * @return {undefined}
1141
- *
1142
- * @example
1143
- * pauseButton.addEventListener('click', () => {
1144
- * recorder.pause()
1145
- * })
1146
- */
1147
- pause() {
1148
- if (this.state !== "recording") return this.em.dispatchEvent(error("pause"));
1149
- this.state = "paused";
1150
- return this.em.dispatchEvent(new Event("pause"));
567
+ const videoSelfieRecordingSetup = setup({
568
+ types: {
569
+ context: {},
570
+ events: {},
571
+ input: {}
572
+ },
573
+ delays: {
574
+ ID_FLIP_DWELL: ID_FLIP_DWELL_MS,
575
+ FINAL_SUCCESS_DWELL: FINAL_SUCCESS_DWELL_MS,
576
+ MAX_VIDEO_LENGTH_ERROR_DWELL: MAX_VIDEO_LENGTH_ERROR_DWELL_MS,
577
+ VALIDATION_FEEDBACK_DWELL: VALIDATION_FEEDBACK_DWELL_MS,
578
+ CAMERA_RELEASE_SETTLE: CAMERA_RELEASE_SETTLE_MS
579
+ },
580
+ actors: {
581
+ prepareWasm: fromPromise(async ({ input }) => input.ensure()),
582
+ visibilityChanges: fromCallback(({ input, sendBack }) => input.visibilityObserver.onVisibilityChange((visible) => {
583
+ sendBack({ type: visible ? "FOREGROUND" : "BACKGROUND" });
584
+ })),
585
+ executionTimer: fromCallback(({ input, sendBack, receive }) => {
586
+ let handle;
587
+ const cancel = () => {
588
+ if (handle !== void 0) {
589
+ input.timer.clearTimeout(handle);
590
+ handle = void 0;
591
+ }
592
+ };
593
+ receive((event) => {
594
+ cancel();
595
+ if (event.type === "ARM") handle = input.timer.setTimeout(() => {
596
+ handle = void 0;
597
+ sendBack({ type: "MAX_VIDEO_LENGTH_REACHED" });
598
+ }, event.durationMs);
599
+ });
600
+ return cancel;
601
+ }),
602
+ acquireCamera: fromPromise(async ({ input, signal }) => {
603
+ const stream = await input.acquire(input.facingMode, input.withAudio);
604
+ if (signal.aborted) {
605
+ stream.getTracks().forEach((track) => track.stop());
606
+ throw new Error("Camera acquire cancelled");
607
+ }
608
+ return stream;
609
+ }),
610
+ switchCamera: fromPromise(async ({ input, signal }) => {
611
+ const dwellCompleted = waitForDwell(input.minDwellMs, signal);
612
+ const stream = await input.acquire(input.facingMode, false);
613
+ if (signal.aborted) {
614
+ stream.getTracks().forEach((track) => track.stop());
615
+ throw new Error("Camera switch cancelled");
616
+ }
617
+ if (!await dwellCompleted) {
618
+ stream.getTracks().forEach((track) => track.stop());
619
+ throw new Error("Camera switch cancelled");
620
+ }
621
+ if (signal.aborted) {
622
+ stream.getTracks().forEach((track) => track.stop());
623
+ throw new Error("Camera switch cancelled");
624
+ }
625
+ return stream;
626
+ }),
627
+ startRecording: fromPromise(async ({ input }) => {
628
+ await input.service.start(input.stream);
629
+ }),
630
+ finalizeRecording: fromPromise(async ({ input }) => input.service.stop()),
631
+ pauseCapture: fromPromise(async ({ input }) => {
632
+ const paused = input.service.pause();
633
+ input.compositor.pause();
634
+ return paused;
635
+ }),
636
+ resumeCompositor: fromPromise(async ({ input, signal }) => input.compositor.resume(signal)),
637
+ resumeRecorder: fromPromise(async ({ input }) => input.service.resume()),
638
+ detectStep: fromCallback(({ input, sendBack }) => {
639
+ const controller = new AbortController();
640
+ input.detect({
641
+ step: input.step,
642
+ stream: input.stream,
643
+ signal: controller.signal,
644
+ onStatus: (status) => sendBack({
645
+ type: "DETECTION_STATUS",
646
+ status
647
+ }),
648
+ onOrientation: (orientation) => sendBack({
649
+ type: "ORIENTATION",
650
+ orientation
651
+ })
652
+ }).then((result) => {
653
+ if (result.outcome === "retryableFailure") {
654
+ sendBack({
655
+ type: "RETRYABLE_FAILURE",
656
+ failure: result.failure
657
+ });
658
+ return;
659
+ }
660
+ if (result.outcome === "validationFailed") {
661
+ sendBack({
662
+ type: "VALIDATION_FAILED",
663
+ failure: result.failure
664
+ });
665
+ return;
666
+ }
667
+ sendBack({ type: "DETECTION_DONE" });
668
+ }).catch((err) => {
669
+ if (!controller.signal.aborted) sendBack({
670
+ type: "DETECTION_FAILED",
671
+ error: String(err),
672
+ kind: isConnectionError(err) ? "connection" : "technical"
673
+ });
674
+ });
675
+ return () => controller.abort();
676
+ })
677
+ },
678
+ actions: {
679
+ armExecutionTimer: sendTo("executionTimer", ({ context }) => ({
680
+ type: "ARM",
681
+ durationMs: context.maxVideoLengthSeconds * 1e3
682
+ })),
683
+ cancelExecutionTimer: sendTo("executionTimer", { type: "CANCEL" }),
684
+ advanceStep: assign({
685
+ stepIndex: ({ context }) => context.stepIndex + 1,
686
+ attemptsRemaining: ({ context }) => context.attemptsPerStep,
687
+ validationFailure: () => void 0,
688
+ cameraSwitchRetried: () => false
689
+ }),
690
+ setDetectionStatus: assign({ detectionStatus: ({ event }) => detectionStatusFromEvent(event) }),
691
+ setOrientation: assign({ orientation: ({ event }) => orientationFromEvent(event) }),
692
+ setDetectionError: assign({ detectionError: ({ event }) => detectionErrorFromEvent(event) }),
693
+ setValidationFailure: assign(({ context, event }) => ({
694
+ attemptsRemaining: context.attemptsRemaining === UNLIMITED_ATTEMPTS ? UNLIMITED_ATTEMPTS : Math.max(0, context.attemptsRemaining - 1),
695
+ validationFailure: validationFailureFromEvent(event),
696
+ detectionStatus: "error"
697
+ })),
698
+ setUncountedRetryableFailure: assign(({ event }) => ({
699
+ validationFailure: retryableFailureFromEvent(event),
700
+ detectionStatus: "error"
701
+ })),
702
+ setErrorFromDetection: assign({
703
+ error: ({ event }) => detectionErrorFromEvent(event),
704
+ errorKind: ({ event }) => errorKindFromEvent(event)
705
+ }),
706
+ clearDetectionFeedback: assign({
707
+ detectionStatus: () => void 0,
708
+ detectionError: () => void 0,
709
+ validationFailure: () => void 0,
710
+ orientation: () => void 0
711
+ }),
712
+ markPageHidden: assign({ isPageVisible: () => false }),
713
+ markPageVisible: assign({ isPageVisible: () => true }),
714
+ prepareRecordingSuspension: assign({
715
+ isPageVisible: () => false,
716
+ resumeState: () => "recording"
717
+ }),
718
+ setUploadFailedError: assign({
719
+ error: () => "Video upload failed",
720
+ errorKind: () => "connection"
721
+ }),
722
+ setMaxVideoLengthReached: assign({
723
+ error: () => "maxVideoLengthReached",
724
+ errorKind: () => "maxVideoLengthReached"
725
+ }),
726
+ setValidationAttemptsExhausted: assign({
727
+ error: () => "Video selfie validation attempts exhausted",
728
+ errorKind: () => "validationAttemptsExhausted"
729
+ }),
730
+ resetSession: assign(({ context }) => ({
731
+ stepIndex: 0,
732
+ cameraSwitchRetried: false,
733
+ stream: void 0,
734
+ composite: void 0,
735
+ detectionStatus: void 0,
736
+ orientation: void 0,
737
+ detectionError: void 0,
738
+ validationFailure: void 0,
739
+ attemptsRemaining: context.attemptsPerStep,
740
+ recordingId: null,
741
+ error: void 0,
742
+ errorKind: void 0,
743
+ resumeState: "recording"
744
+ })),
745
+ cleanup: ({ context }) => {
746
+ context.service.cleanup();
747
+ context.compositor.stop();
748
+ context.stream?.getTracks().forEach((track) => track.stop());
749
+ },
750
+ setTechnicalActorError: assign({
751
+ error: ({ event }) => errorFromEvent(event),
752
+ errorKind: () => "technical"
753
+ }),
754
+ setAudioEndedError: assign({
755
+ error: () => "Audio track ended while the app was backgrounded",
756
+ errorKind: () => "technical"
757
+ }),
758
+ setCameraResumeError: assign({
759
+ error: () => "Camera did not produce a fresh frame after foregrounding",
760
+ errorKind: () => "technical"
761
+ }),
762
+ setRecorderResumeError: assign({
763
+ error: () => "Recording could not resume after foregrounding",
764
+ errorKind: () => "technical"
765
+ }),
766
+ setPauseError: assign({
767
+ error: () => "Recording could not be paused before backgrounding",
768
+ errorKind: () => "technical"
769
+ }),
770
+ releaseCurrentVideo: ({ context }) => {
771
+ context.stream?.getVideoTracks().forEach((track) => track.stop());
772
+ }
773
+ },
774
+ guards: {
775
+ shouldEnterWasmGate: ({ context }) => context.ensureWasmReady !== void 0 && context.errorKind !== "maxVideoLengthReached" && context.errorKind !== "validationAttemptsExhausted",
776
+ isLastStep: ({ context }) => context.stepIndex >= context.steps.length - 1,
777
+ hasAttemptsRemaining: ({ context }) => context.attemptsRemaining === UNLIMITED_ATTEMPTS || context.attemptsRemaining > 0,
778
+ nextStepFlipsId: ({ context }) => context.steps[context.stepIndex] === "frontId" && context.steps[context.stepIndex + 1] === "backId",
779
+ nextStepFlipsCamera: ({ context }) => facingModeForStep(context.steps[context.stepIndex]) !== facingModeForStep(context.steps[context.stepIndex + 1]),
780
+ uploadReturnedNoRecordingId: ({ event }) => recordingIdFromEvent(event) === null,
781
+ maxVideoLengthWasReached: ({ context }) => context.errorKind === "maxVideoLengthReached",
782
+ validationAttemptsWereExhausted: ({ context }) => context.errorKind === "validationAttemptsExhausted",
783
+ errorIsRetryable: ({ context }) => context.errorKind !== "maxVideoLengthReached" && context.errorKind !== "validationAttemptsExhausted",
784
+ canRetryCameraSwitchAfterRelease: ({ context, event }) => !context.cameraSwitchRetried && isCameraBusyError(event.error),
785
+ pageIsHidden: ({ context }) => !context.isPageVisible,
786
+ pauseFailed: ({ event }) => !event.output,
787
+ pauseSucceededWhileVisible: ({ context, event }) => context.isPageVisible && event.output,
788
+ compositorAudioEnded: ({ event }) => event.output === "audio-ended",
789
+ compositorDidNotResume: ({ event }) => event.output !== "resumed",
790
+ forceVideoRecovery: ({ context }) => context.resumeState === "switchingCamera",
791
+ recorderResumeFailed: ({ event }) => !event.output,
792
+ resumeValidationFeedback: ({ context }) => context.resumeState === "validationFeedback",
793
+ resumeFlippingId: ({ context }) => context.resumeState === "flippingId"
1151
794
  }
1152
- /**
1153
- * Resumes media recording when it has been previously paused.
1154
- *
1155
- * @return {undefined}
1156
- *
1157
- * @example
1158
- * resumeButton.addEventListener('click', () => {
1159
- * recorder.resume()
1160
- * })
1161
- */
1162
- resume() {
1163
- if (this.state !== "paused") return this.em.dispatchEvent(error("resume"));
1164
- this.state = "recording";
1165
- return this.em.dispatchEvent(new Event("resume"));
1166
- }
1167
- /**
1168
- * Raise a `dataavailable` event containing the captured media.
1169
- *
1170
- * @return {undefined}
1171
- *
1172
- * @example
1173
- * this.on('nextData', () => {
1174
- * recorder.requestData()
1175
- * })
1176
- */
1177
- requestData() {
1178
- if (this.state === "inactive") return this.em.dispatchEvent(error("requestData"));
1179
- return this.encoder.postMessage(["dump", context.sampleRate]);
1180
- }
1181
- /**
1182
- * Add listener for specified event type.
1183
- *
1184
- * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
1185
- * type Event type.
1186
- * @param {function} listener The listener function.
1187
- *
1188
- * @return {undefined}
1189
- *
1190
- * @example
1191
- * recorder.addEventListener('dataavailable', e => {
1192
- * audio.src = URL.createObjectURL(e.data)
1193
- * })
1194
- */
1195
- addEventListener(...args) {
1196
- this.em.addEventListener(...args);
1197
- }
1198
- /**
1199
- * Remove event listener.
1200
- *
1201
- * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
1202
- * type Event type.
1203
- * @param {function} listener The same function used in `addEventListener`.
1204
- *
1205
- * @return {undefined}
1206
- */
1207
- removeEventListener(...args) {
1208
- this.em.removeEventListener(...args);
795
+ });
796
+ const ADVANCE_TRANSITIONS = [
797
+ {
798
+ target: "finalizing",
799
+ guard: "isLastStep"
800
+ },
801
+ {
802
+ target: "flippingId",
803
+ guard: "nextStepFlipsId"
804
+ },
805
+ {
806
+ target: "switchingCamera",
807
+ guard: "nextStepFlipsCamera",
808
+ actions: "advanceStep"
809
+ },
810
+ {
811
+ target: "recording",
812
+ reenter: true,
813
+ actions: "advanceStep"
1209
814
  }
1210
- /**
1211
- * Calls each of the listeners registered for a given event.
1212
- *
1213
- * @param {Event} event The event object.
1214
- *
1215
- * @return {boolean} Is event was no canceled by any listener.
1216
- */
1217
- dispatchEvent(...args) {
1218
- this.em.dispatchEvent(...args);
815
+ ];
816
+ const videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
817
+ id: "videoSelfieRecording",
818
+ initial: "idle",
819
+ context: ({ input }) => {
820
+ const visibilityObserver = input.visibilityObserver;
821
+ const compositor = input.dependencies?.createCompositor?.() ?? createRecordingCompositor();
822
+ return {
823
+ service: input.dependencies?.createRecordingService?.() ?? new MultipartRecordingService("videoselfie", new WorkerEncoderRecordingProvider(compositor)),
824
+ compositor,
825
+ acquireCamera: input.dependencies?.acquireCamera ?? defaultAcquireCamera,
826
+ ensureWasmReady: input.ensureWasmReady,
827
+ detect: input.dependencies?.detect ?? manualStepDetector,
828
+ timer: input.dependencies?.timer ?? BrowserTimerProvider.getInstance(),
829
+ visibilityObserver,
830
+ isPageVisible: visibilityObserver.isVisible(),
831
+ resumeState: "recording",
832
+ steps: input.steps && input.steps.length > 0 ? input.steps : ["selfie"],
833
+ stepIndex: 0,
834
+ stream: void 0,
835
+ composite: void 0,
836
+ detectionStatus: void 0,
837
+ orientation: void 0,
838
+ detectionError: void 0,
839
+ validationFailure: void 0,
840
+ attemptsPerStep: normalizeAttempts(input.attemptsPerStep),
841
+ attemptsRemaining: normalizeAttempts(input.attemptsPerStep),
842
+ maxVideoLengthSeconds: normalizeMaxVideoLength(input.maxVideoLengthSeconds),
843
+ recordingId: null,
844
+ error: void 0,
845
+ errorKind: void 0,
846
+ switchingCameraMinDwellMs: input.dependencies?.switchingCameraMinDwellMs ?? SWITCHING_CAMERA_MIN_DWELL_MS,
847
+ cameraSwitchRetried: false
848
+ };
849
+ },
850
+ invoke: [{
851
+ id: "executionTimer",
852
+ src: "executionTimer",
853
+ input: ({ context }) => ({ timer: context.timer })
854
+ }, {
855
+ id: "visibilityChanges",
856
+ src: "visibilityChanges",
857
+ input: ({ context }) => ({ visibilityObserver: context.visibilityObserver })
858
+ }],
859
+ on: {
860
+ ABORT: { target: ".aborted" },
861
+ BACKGROUND: { actions: "markPageHidden" },
862
+ FOREGROUND: { actions: "markPageVisible" }
863
+ },
864
+ states: {
865
+ idle: { on: { START: [{
866
+ target: "preparingWasm",
867
+ guard: "shouldEnterWasmGate"
868
+ }, { target: "acquiringCamera" }] } },
869
+ preparingWasm: { invoke: {
870
+ src: "prepareWasm",
871
+ input: ({ context }) => ({ ensure: context.ensureWasmReady }),
872
+ onDone: { target: "acquiringCamera" },
873
+ onError: {
874
+ target: "error",
875
+ actions: "setTechnicalActorError"
876
+ }
877
+ } },
878
+ acquiringCamera: { invoke: {
879
+ src: "acquireCamera",
880
+ input: ({ context }) => ({
881
+ acquire: context.acquireCamera,
882
+ facingMode: facingModeForStep(context.steps[context.stepIndex]),
883
+ withAudio: true
884
+ }),
885
+ onDone: {
886
+ target: "starting",
887
+ actions: [({ context, event }) => {
888
+ context.compositor.start(eventOutputStream(event));
889
+ }, assign({
890
+ stream: ({ event }) => eventOutputStream(event),
891
+ composite: ({ context }) => context.compositor.stream
892
+ })]
893
+ },
894
+ onError: {
895
+ target: "error",
896
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
897
+ }
898
+ } },
899
+ starting: { invoke: {
900
+ src: "startRecording",
901
+ input: ({ context }) => ({
902
+ service: context.service,
903
+ stream: context.composite
904
+ }),
905
+ onDone: [{
906
+ target: "pausing",
907
+ guard: "pageIsHidden",
908
+ actions: ["armExecutionTimer", "prepareRecordingSuspension"]
909
+ }, {
910
+ target: "recording",
911
+ actions: "armExecutionTimer"
912
+ }],
913
+ onError: {
914
+ target: "error",
915
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
916
+ }
917
+ } },
918
+ recording: {
919
+ entry: "clearDetectionFeedback",
920
+ invoke: {
921
+ src: "detectStep",
922
+ input: ({ context }) => ({
923
+ detect: context.detect,
924
+ step: context.steps[context.stepIndex],
925
+ stream: context.stream
926
+ })
927
+ },
928
+ on: {
929
+ BACKGROUND: {
930
+ target: "pausing",
931
+ actions: "prepareRecordingSuspension"
932
+ },
933
+ MAX_VIDEO_LENGTH_REACHED: {
934
+ target: "finalizing",
935
+ actions: "setMaxVideoLengthReached"
936
+ },
937
+ DETECTION_STATUS: { actions: "setDetectionStatus" },
938
+ ORIENTATION: { actions: "setOrientation" },
939
+ DETECTION_DONE: ADVANCE_TRANSITIONS,
940
+ NEXT_STEP: ADVANCE_TRANSITIONS,
941
+ VALIDATION_FAILED: {
942
+ target: "validationFeedback",
943
+ actions: "setValidationFailure"
944
+ },
945
+ RETRYABLE_FAILURE: {
946
+ target: "validationFeedback",
947
+ actions: "setUncountedRetryableFailure"
948
+ },
949
+ DETECTION_FAILED: {
950
+ target: "error",
951
+ actions: "setErrorFromDetection"
952
+ }
953
+ }
954
+ },
955
+ validationFeedback: {
956
+ on: {
957
+ BACKGROUND: {
958
+ target: "pausing",
959
+ actions: ["markPageHidden", assign({ resumeState: () => "validationFeedback" })]
960
+ },
961
+ MAX_VIDEO_LENGTH_REACHED: {
962
+ target: "finalizing",
963
+ actions: "setMaxVideoLengthReached"
964
+ }
965
+ },
966
+ after: { VALIDATION_FEEDBACK_DWELL: [{
967
+ target: "recording",
968
+ guard: "hasAttemptsRemaining"
969
+ }, {
970
+ target: "finalizing",
971
+ actions: "setValidationAttemptsExhausted"
972
+ }] }
973
+ },
974
+ flippingId: {
975
+ after: { ID_FLIP_DWELL: {
976
+ target: "recording",
977
+ actions: "advanceStep"
978
+ } },
979
+ on: {
980
+ BACKGROUND: {
981
+ target: "pausing",
982
+ actions: ["markPageHidden", assign({ resumeState: () => "flippingId" })]
983
+ },
984
+ MAX_VIDEO_LENGTH_REACHED: {
985
+ target: "finalizing",
986
+ actions: "setMaxVideoLengthReached"
987
+ },
988
+ NEXT_STEP: {
989
+ target: "recording",
990
+ actions: "advanceStep"
991
+ }
992
+ }
993
+ },
994
+ switchingCamera: {
995
+ on: {
996
+ BACKGROUND: {
997
+ target: "pausing",
998
+ actions: ["markPageHidden", assign({ resumeState: () => "switchingCamera" })]
999
+ },
1000
+ MAX_VIDEO_LENGTH_REACHED: {
1001
+ target: "finalizing",
1002
+ actions: "setMaxVideoLengthReached"
1003
+ }
1004
+ },
1005
+ invoke: {
1006
+ src: "switchCamera",
1007
+ input: ({ context }) => ({
1008
+ acquire: context.acquireCamera,
1009
+ facingMode: facingModeForStep(context.steps[context.stepIndex]),
1010
+ minDwellMs: context.switchingCameraMinDwellMs
1011
+ }),
1012
+ onDone: {
1013
+ target: "recording",
1014
+ actions: [({ context, event }) => {
1015
+ context.compositor.switchVideo(eventOutputStream(event));
1016
+ }, assign({ stream: ({ event }) => eventOutputStream(event) })]
1017
+ },
1018
+ onError: [{
1019
+ target: "releasingCamera",
1020
+ guard: "canRetryCameraSwitchAfterRelease"
1021
+ }, {
1022
+ target: "error",
1023
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
1024
+ }]
1025
+ }
1026
+ },
1027
+ releasingCamera: {
1028
+ entry: ["releaseCurrentVideo", assign({ cameraSwitchRetried: () => true })],
1029
+ on: {
1030
+ BACKGROUND: {
1031
+ target: "pausing",
1032
+ actions: ["markPageHidden", assign({ resumeState: () => "switchingCamera" })]
1033
+ },
1034
+ MAX_VIDEO_LENGTH_REACHED: {
1035
+ target: "finalizing",
1036
+ actions: "setMaxVideoLengthReached"
1037
+ }
1038
+ },
1039
+ after: { CAMERA_RELEASE_SETTLE: { target: "switchingCamera" } }
1040
+ },
1041
+ pausing: {
1042
+ on: { MAX_VIDEO_LENGTH_REACHED: { actions: "setMaxVideoLengthReached" } },
1043
+ invoke: {
1044
+ src: "pauseCapture",
1045
+ input: ({ context }) => ({
1046
+ service: context.service,
1047
+ compositor: context.compositor
1048
+ }),
1049
+ onDone: [
1050
+ {
1051
+ target: "finalizing",
1052
+ guard: "maxVideoLengthWasReached"
1053
+ },
1054
+ {
1055
+ target: "error",
1056
+ guard: "pauseFailed",
1057
+ actions: "setPauseError"
1058
+ },
1059
+ {
1060
+ target: "resumingCompositor",
1061
+ guard: "pauseSucceededWhileVisible"
1062
+ },
1063
+ { target: "suspended" }
1064
+ ],
1065
+ onError: [{
1066
+ target: "finalizing",
1067
+ guard: "maxVideoLengthWasReached"
1068
+ }, {
1069
+ target: "error",
1070
+ actions: "setTechnicalActorError"
1071
+ }]
1072
+ }
1073
+ },
1074
+ suspended: { on: {
1075
+ FOREGROUND: {
1076
+ target: "resumingCompositor",
1077
+ actions: "markPageVisible"
1078
+ },
1079
+ MAX_VIDEO_LENGTH_REACHED: {
1080
+ target: "finalizing",
1081
+ actions: "setMaxVideoLengthReached"
1082
+ }
1083
+ } },
1084
+ resumingCompositor: {
1085
+ on: {
1086
+ BACKGROUND: {
1087
+ target: "pausing",
1088
+ actions: "markPageHidden"
1089
+ },
1090
+ MAX_VIDEO_LENGTH_REACHED: {
1091
+ target: "finalizing",
1092
+ actions: "setMaxVideoLengthReached"
1093
+ }
1094
+ },
1095
+ invoke: {
1096
+ src: "resumeCompositor",
1097
+ input: ({ context }) => ({ compositor: context.compositor }),
1098
+ onDone: [
1099
+ {
1100
+ target: "error",
1101
+ guard: "compositorAudioEnded",
1102
+ actions: "setAudioEndedError"
1103
+ },
1104
+ {
1105
+ target: "reacquiringCamera",
1106
+ guard: "forceVideoRecovery"
1107
+ },
1108
+ {
1109
+ target: "reacquiringCamera",
1110
+ guard: "compositorDidNotResume"
1111
+ },
1112
+ { target: "resumingRecorder" }
1113
+ ],
1114
+ onError: {
1115
+ target: "error",
1116
+ actions: "setTechnicalActorError"
1117
+ }
1118
+ }
1119
+ },
1120
+ reacquiringCamera: {
1121
+ on: {
1122
+ BACKGROUND: {
1123
+ target: "pausing",
1124
+ actions: "markPageHidden"
1125
+ },
1126
+ MAX_VIDEO_LENGTH_REACHED: {
1127
+ target: "finalizing",
1128
+ actions: "setMaxVideoLengthReached"
1129
+ }
1130
+ },
1131
+ invoke: {
1132
+ src: "acquireCamera",
1133
+ input: ({ context }) => ({
1134
+ acquire: context.acquireCamera,
1135
+ facingMode: facingModeForStep(context.steps[context.stepIndex]),
1136
+ withAudio: false
1137
+ }),
1138
+ onDone: {
1139
+ target: "validatingRecoveredCamera",
1140
+ actions: [({ context, event }) => {
1141
+ context.compositor.switchVideo(eventOutputStream(event));
1142
+ }, assign({ stream: ({ event }) => eventOutputStream(event) })]
1143
+ },
1144
+ onError: {
1145
+ target: "error",
1146
+ actions: "setTechnicalActorError"
1147
+ }
1148
+ }
1149
+ },
1150
+ validatingRecoveredCamera: {
1151
+ on: {
1152
+ BACKGROUND: {
1153
+ target: "pausing",
1154
+ actions: "markPageHidden"
1155
+ },
1156
+ MAX_VIDEO_LENGTH_REACHED: {
1157
+ target: "finalizing",
1158
+ actions: "setMaxVideoLengthReached"
1159
+ }
1160
+ },
1161
+ invoke: {
1162
+ src: "resumeCompositor",
1163
+ input: ({ context }) => ({ compositor: context.compositor }),
1164
+ onDone: [
1165
+ {
1166
+ target: "error",
1167
+ guard: "compositorAudioEnded",
1168
+ actions: "setAudioEndedError"
1169
+ },
1170
+ {
1171
+ target: "error",
1172
+ guard: "compositorDidNotResume",
1173
+ actions: "setCameraResumeError"
1174
+ },
1175
+ { target: "resumingRecorder" }
1176
+ ],
1177
+ onError: {
1178
+ target: "error",
1179
+ actions: "setTechnicalActorError"
1180
+ }
1181
+ }
1182
+ },
1183
+ resumingRecorder: {
1184
+ on: {
1185
+ BACKGROUND: {
1186
+ target: "pausing",
1187
+ actions: "markPageHidden"
1188
+ },
1189
+ MAX_VIDEO_LENGTH_REACHED: {
1190
+ target: "finalizing",
1191
+ actions: "setMaxVideoLengthReached"
1192
+ }
1193
+ },
1194
+ invoke: {
1195
+ src: "resumeRecorder",
1196
+ input: ({ context }) => ({ service: context.service }),
1197
+ onDone: [
1198
+ {
1199
+ target: "error",
1200
+ guard: "recorderResumeFailed",
1201
+ actions: "setRecorderResumeError"
1202
+ },
1203
+ {
1204
+ target: "validationFeedback",
1205
+ guard: "resumeValidationFeedback"
1206
+ },
1207
+ {
1208
+ target: "flippingId",
1209
+ guard: "resumeFlippingId"
1210
+ },
1211
+ { target: "recording" }
1212
+ ],
1213
+ onError: {
1214
+ target: "error",
1215
+ actions: "setTechnicalActorError"
1216
+ }
1217
+ }
1218
+ },
1219
+ finalizing: {
1220
+ entry: "cancelExecutionTimer",
1221
+ invoke: {
1222
+ src: "finalizeRecording",
1223
+ input: ({ context }) => ({ service: context.service }),
1224
+ onDone: [
1225
+ {
1226
+ target: "error",
1227
+ guard: "maxVideoLengthWasReached",
1228
+ actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
1229
+ },
1230
+ {
1231
+ target: "error",
1232
+ guard: "validationAttemptsWereExhausted",
1233
+ actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
1234
+ },
1235
+ {
1236
+ target: "error",
1237
+ guard: "uploadReturnedNoRecordingId",
1238
+ actions: [assign({ recordingId: ({ event }) => recordingIdFromEvent(event) }), "setUploadFailedError"]
1239
+ },
1240
+ {
1241
+ target: "success",
1242
+ actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
1243
+ }
1244
+ ],
1245
+ onError: [
1246
+ {
1247
+ target: "error",
1248
+ guard: "maxVideoLengthWasReached"
1249
+ },
1250
+ {
1251
+ target: "error",
1252
+ guard: "validationAttemptsWereExhausted"
1253
+ },
1254
+ {
1255
+ target: "error",
1256
+ actions: assign({ error: ({ event }) => errorFromEvent(event) })
1257
+ }
1258
+ ]
1259
+ }
1260
+ },
1261
+ success: {
1262
+ entry: ["cancelExecutionTimer", "cleanup"],
1263
+ after: { FINAL_SUCCESS_DWELL: "finished" }
1264
+ },
1265
+ finished: { type: "final" },
1266
+ error: {
1267
+ entry: ["cancelExecutionTimer", "cleanup"],
1268
+ after: { MAX_VIDEO_LENGTH_ERROR_DWELL: {
1269
+ target: "finished",
1270
+ guard: "maxVideoLengthWasReached"
1271
+ } },
1272
+ on: { RETRY: [{
1273
+ target: "preparingWasm",
1274
+ guard: "shouldEnterWasmGate",
1275
+ actions: "resetSession"
1276
+ }, {
1277
+ target: "acquiringCamera",
1278
+ guard: "errorIsRetryable",
1279
+ actions: "resetSession"
1280
+ }] }
1281
+ },
1282
+ aborted: {
1283
+ type: "final",
1284
+ entry: ["cancelExecutionTimer", "cleanup"]
1285
+ }
1219
1286
  }
1220
- };
1221
- /**
1222
- * The MIME type that is being used for recording.
1223
- * @type {string}
1224
- */
1225
- MediaRecorder.prototype.mimeType = "audio/wav";
1287
+ });
1288
+ //#endregion
1289
+ //#region src/modules/video-selfie/videoSelfieRecordingActor.ts
1290
+ function getRecordingSteps(options) {
1291
+ if (options.steps && options.steps.length > 0) return options.steps;
1292
+ if (options.config) return resolveVideoSelfieSteps(options.config);
1293
+ }
1294
+ function createRetryablePreparation(prepare) {
1295
+ let readiness;
1296
+ return () => {
1297
+ if (!readiness) {
1298
+ readiness = Promise.resolve().then(prepare).catch((error) => {
1299
+ readiness = void 0;
1300
+ throw error;
1301
+ });
1302
+ readiness.catch(() => void 0);
1303
+ }
1304
+ return readiness;
1305
+ };
1306
+ }
1307
+ function createVideoSelfieRecordingActor(visibilityObserver, options = {}) {
1308
+ const config = options.config;
1309
+ const steps = getRecordingSteps(options);
1310
+ const detect = options.dependencies?.detect ?? (config ? createDefaultStepDetector(config, { getWasmUtil: () => WasmUtilProvider.getInstance() }) : void 0);
1311
+ const prepareWasm = config && steps ? () => warmupVideoSelfieWasm(config, steps) : void 0;
1312
+ const ensureWasmReady = prepareWasm ? createRetryablePreparation(prepareWasm) : void 0;
1313
+ if (ensureWasmReady) ensureWasmReady();
1314
+ if (steps?.includes("speech")) VoiceRecordingProvider.preload();
1315
+ return createActor(videoSelfieRecordingMachine, { input: {
1316
+ steps,
1317
+ attemptsPerStep: config?.numberOfTries,
1318
+ maxVideoLengthSeconds: config?.maxWaitingVideoSelfieFile,
1319
+ visibilityObserver,
1320
+ ensureWasmReady,
1321
+ dependencies: {
1322
+ ...options.dependencies,
1323
+ detect
1324
+ }
1325
+ } }).start();
1326
+ }
1327
+ //#endregion
1328
+ //#region src/modules/video-selfie/videoSelfieRecordingManager.ts
1329
+ function mapState(snapshot) {
1330
+ const value = typeof snapshot.value === "string" ? snapshot.value : "idle";
1331
+ const { context } = snapshot;
1332
+ const status = value === "preparingWasm" || value === "acquiringCamera" ? "starting" : value === "pausing" || value === "suspended" || value === "resumingCompositor" || value === "reacquiringCamera" || value === "validatingRecoveredCamera" || value === "resumingRecorder" ? "recording" : value === "releasingCamera" ? "switchingCamera" : value;
1333
+ const steps = context.steps;
1334
+ const stepIndex = context.stepIndex;
1335
+ return {
1336
+ status,
1337
+ step: steps[stepIndex],
1338
+ stepIndex,
1339
+ isLastStep: stepIndex >= steps.length - 1,
1340
+ stream: context.stream,
1341
+ composite: context.composite,
1342
+ detectionStatus: context.detectionStatus,
1343
+ orientation: context.orientation,
1344
+ validationFailure: context.validationFailure,
1345
+ attemptsRemaining: context.attemptsRemaining,
1346
+ recordingId: context.recordingId,
1347
+ error: context.error,
1348
+ errorKind: context.errorKind
1349
+ };
1350
+ }
1351
+ function createApi({ actor }) {
1352
+ return {
1353
+ /** Acquire the first camera, open the upload, and begin recording. */
1354
+ start() {
1355
+ actor.send({ type: "START" });
1356
+ },
1357
+ retry() {
1358
+ actor.send({ type: "RETRY" });
1359
+ },
1360
+ /**
1361
+ * Advance to the next step. Switches the camera when the next step's
1362
+ * facing mode differs; finalizes the upload on the last step.
1363
+ */
1364
+ nextStep() {
1365
+ actor.send({ type: "NEXT_STEP" });
1366
+ },
1367
+ /** Abandon the session without finalizing the upload. */
1368
+ abort() {
1369
+ actor.send({ type: "ABORT" });
1370
+ }
1371
+ };
1372
+ }
1226
1373
  /**
1227
- * Returns `true` if the MIME type specified is one the polyfill can record.
1228
- *
1229
- * This polyfill supports `audio/wav` and `audio/mpeg`.
1230
- *
1231
- * @param {string} mimeType The mimeType to check.
1374
+ * Creates a manager for a multi-step, camera-switching Video Selfie recording.
1232
1375
  *
1233
- * @return {boolean} `true` on `audio/wav` and `audio/mpeg` MIME type.
1376
+ * The whole session records as one continuous video that follows the camera
1377
+ * across steps (front for selfie, rear for ID). Exposes `getState()` /
1378
+ * `subscribe()` (status, step, preview `stream`, `recordingId`) plus `start()`,
1379
+ * `nextStep()`, and `abort()`.
1234
1380
  */
1235
- MediaRecorder.isTypeSupported = (mimeType) => {
1236
- return MediaRecorder.prototype.mimeType === mimeType;
1237
- };
1381
+ function createVideoSelfieRecordingManager(options = {}) {
1382
+ const actor = createVideoSelfieRecordingActor(options.dependencies?.visibilityObserver ?? new BrowserVisibilityObserverProvider(), options);
1383
+ return createManager({
1384
+ actor,
1385
+ mapState,
1386
+ createApi,
1387
+ beforeStop: () => actor.send({ type: "ABORT" })
1388
+ });
1389
+ }
1390
+ //#endregion
1391
+ //#region src/modules/video-selfie/videoSelfieSpeechService.ts
1238
1392
  /**
1239
- * `true` if MediaRecorder can not be polyfilled in the current browser.
1240
- * @type {boolean}
1241
- *
1242
- * @example
1243
- * if (MediaRecorder.notSupported) {
1244
- * showWarning('Audio recording is not supported in this browser')
1245
- * }
1393
+ * Fetches `count` random challenge questions the user answers out loud. The
1394
+ * spoken answers are captured in the main video recording (no separate upload)
1395
+ * and verified server-side.
1246
1396
  */
1247
- MediaRecorder.notSupported = !navigator.mediaDevices || !AudioContext;
1397
+ async function getVideoSelfieQuestions(count) {
1398
+ return (await api.get(endpoints.questionAndAnswer, { query: {
1399
+ numberOfQuestions: count,
1400
+ returnVoiceConsentQuestion: false
1401
+ } })).data.questionAndAnswers ?? [];
1402
+ }
1248
1403
  /**
1249
- * Converts RAW audio buffer to compressed audio files.
1250
- * It will be loaded to Web Worker.
1251
- * By default, WAVE encoder will be used.
1252
- * @type {function}
1253
- *
1254
- * @example
1255
- * MediaRecorder.prototype.mimeType = 'audio/ogg'
1256
- * MediaRecorder.encoder = oggEncoder
1404
+ * Fetches the voice-consent phrase the user must read aloud. The phrase is the
1405
+ * `answer` of the consent question returned when `returnVoiceConsentQuestion`
1406
+ * is requested with no challenge questions.
1257
1407
  */
1258
- MediaRecorder.encoder = wave_encoder_default;
1259
- //#endregion
1260
- //#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/mpeg-encoder/index.js
1261
- var mpeg_encoder_default = () => {
1262
- importScripts("https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.0/lame.min.js");
1263
- let CHANNELS = 1;
1264
- let KBPS = 128;
1265
- let DEFAULT_SAMPLE_RATE = 44100;
1266
- let encoder;
1267
- let recorded = /* @__PURE__ */ new Int8Array();
1268
- function concat(a, b) {
1269
- if (b.length === 0) return a;
1270
- let c = new Int8Array(a.length + b.length);
1271
- c.set(a);
1272
- c.set(b, a.length);
1273
- return c;
1274
- }
1275
- function init(sampleRate) {
1276
- encoder = new lamejs.Mp3Encoder(CHANNELS, sampleRate || DEFAULT_SAMPLE_RATE, KBPS);
1277
- }
1278
- function encode(buffer) {
1279
- for (let i = 0; i < buffer.length; i++) buffer[i] = buffer[i] * 32767.5;
1280
- let buf = encoder.encodeBuffer(buffer);
1281
- recorded = concat(recorded, buf);
1282
- }
1283
- function dump() {
1284
- let buf = encoder.flush();
1285
- recorded = concat(recorded, buf);
1286
- let buffer = recorded.buffer;
1287
- recorded = /* @__PURE__ */ new Int8Array();
1288
- postMessage(buffer, [buffer]);
1289
- }
1290
- onmessage = (e) => {
1291
- if (e.data[0] === "init") init(e.data[1]);
1292
- else if (e.data[0] === "encode") encode(e.data[1]);
1293
- else dump(e.data[1]);
1294
- };
1295
- };
1296
- //#endregion
1297
- //#region ../infra/src/media/blobEncoding.ts
1298
- /** Encodes a blob as raw base64 bytes (no `data:...;base64,` prefix). */
1299
- function blobToRawBase64(blob) {
1300
- return new Promise((resolve, reject) => {
1301
- const reader = new FileReader();
1302
- reader.onloadend = () => {
1303
- const result = reader.result;
1304
- if (typeof result !== "string") {
1305
- reject(/* @__PURE__ */ new Error("Failed to encode blob as base64"));
1306
- return;
1307
- }
1308
- const commaIndex = result.indexOf(",");
1309
- resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result);
1310
- };
1311
- reader.onerror = () => {
1312
- reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read blob"));
1313
- };
1314
- reader.readAsDataURL(blob);
1315
- });
1408
+ async function getVoiceConsentPhrase() {
1409
+ return (await api.get(endpoints.questionAndAnswer, { query: {
1410
+ numberOfQuestions: 0,
1411
+ returnVoiceConsentQuestion: true
1412
+ } })).data.questionAndAnswers?.[0]?.answer ?? "";
1316
1413
  }
1317
- //#endregion
1318
- //#region ../infra/src/providers/browser/VoiceRecordingProvider.ts
1319
- const AudioRecorderPolyfill = MediaRecorder;
1320
- AudioRecorderPolyfill.encoder = mpeg_encoder_default;
1321
- AudioRecorderPolyfill.prototype.mimeType = "audio/mpeg";
1322
- const MIME_TYPE = "audio/mpeg";
1323
1414
  /**
1324
- * Records the audio of a stream and produces a single MP3 blob. Used by the
1325
- * Video Selfie voice-consent step to capture the spoken consent phrase
1326
- * independently of the main video recording (which keeps running on its own
1327
- * recorder).
1328
- *
1329
- * Records from a clone of the supplied stream so stopping this recorder never
1330
- * tears down tracks the main recording is still using.
1415
+ * Uploads the recorded consent audio (base64 MP3) for speech-to-text
1416
+ * validation. Resolves `{ success: false }` on a network/validation error so
1417
+ * the UI can drive its retry loop instead of throwing.
1331
1418
  */
1332
- var VoiceRecordingProvider = class {
1333
- constructor() {
1334
- this.recorder = null;
1335
- this.chunks = [];
1336
- this._isRecording = false;
1337
- }
1338
- get isRecording() {
1339
- return this._isRecording;
1340
- }
1341
- start(stream) {
1342
- this.reset();
1343
- this.chunks = [];
1344
- const audioOnly = new MediaStream(stream.getAudioTracks());
1345
- const recorder = new AudioRecorderPolyfill(audioOnly.clone());
1346
- recorder.mimeType = MIME_TYPE;
1347
- recorder.addEventListener("dataavailable", (event) => {
1348
- if (event.data.size > 0) this.chunks.push(event.data);
1349
- });
1350
- recorder.start();
1351
- this.recorder = recorder;
1352
- this._isRecording = true;
1353
- }
1354
- stop() {
1355
- const recorder = this.recorder;
1356
- return new Promise((resolve) => {
1357
- if (!recorder || !this._isRecording) {
1358
- resolve(new Blob([], { type: MIME_TYPE }));
1359
- return;
1360
- }
1361
- recorder.addEventListener("stop", () => {
1362
- this._isRecording = false;
1363
- recorder.stream?.getTracks().forEach((track) => track.stop());
1364
- this.recorder = null;
1365
- resolve(new Blob(this.chunks, { type: MIME_TYPE }));
1366
- });
1367
- recorder.stop();
1368
- });
1369
- }
1370
- async stopAsBase64() {
1371
- const blob = await this.stop();
1372
- if (blob.size === 0) return "";
1373
- return blobToRawBase64(blob);
1374
- }
1375
- reset() {
1376
- const recorder = this.recorder;
1377
- if (recorder && this._isRecording) {
1378
- try {
1379
- recorder.stop();
1380
- } catch {}
1381
- recorder.stream?.getTracks().forEach((track) => track.stop());
1382
- }
1383
- this.recorder = null;
1384
- this._isRecording = false;
1419
+ async function addSpeech(base64Mp3) {
1420
+ try {
1421
+ return { success: (await api.post(endpoints.addSpeechV2, {
1422
+ base64Media: base64Mp3,
1423
+ audioOnly: true
1424
+ })).data.success === true };
1425
+ } catch {
1426
+ return { success: false };
1385
1427
  }
1386
- };
1428
+ }
1387
1429
  //#endregion
1388
1430
  //#region src/modules/video-selfie/videoSelfieConsentRecorder.ts
1389
1431
  function createConsentRecorder() {