@incodetech/core 2.1.0-rc.2 → 2.1.0-rc.3
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.
- package/dist/3dAvatarProvider-ChciZmjE.esm.js +28214 -0
- package/dist/{BrowserStorageProvider-D5xc7Lqo.esm.js → BrowserStorageProvider-CrrN540a.esm.js} +1 -25
- package/dist/BrowserVisibilityObserverProvider-rim9Jrz-.esm.js +25 -0
- package/dist/Face2DProvider-BeLlYBbB.esm.js +824 -0
- package/dist/FaceDetectionProvider-B78455D4.d.ts +119 -0
- package/dist/IVisibilityObserverCapability-BbWjgpiF.d.ts +12 -0
- package/dist/IWasmUtilCapability-d2XvzaCk.d.ts +145 -0
- package/dist/IpifyProvider-BRW14_uo.esm.js +51 -0
- package/dist/{Manager-B2OEDYqf.d.ts → Manager-CJ3G2zqi.d.ts} +1 -4
- package/dist/{OpenViduRecordingProvider-DF7A4ZNJ.esm.js → OpenViduRecordingProvider-B_0GKkzc.esm.js} +1 -1
- package/dist/PrivacyLensProvider-hDzaDzZ6.esm.js +339 -0
- package/dist/StateMachine-C_AY4cs7.d.ts +2 -0
- package/dist/{StreamCanvasCapture-Dr5n_iK1.esm.js → StreamCanvasCapture-Bz17q4FP.esm.js} +1 -1
- package/dist/StreamCanvasCapture-DS1TOoiH.d.ts +42 -0
- package/dist/{WasmUtilProvider-CIu373BZ.esm.js → WasmUtilProvider-ADG54T6M.esm.js} +16 -1
- package/dist/{addressSearch-BH96a3fq.esm.js → addressSearch-DJyNTg5S.esm.js} +4 -4
- package/dist/{ae-signature-CDEN8y-w.esm.js → ae-signature-DlElqFrZ.esm.js} +1 -1
- package/dist/ae-signature.d.ts +4 -3
- package/dist/ae-signature.esm.js +10 -9
- package/dist/antifraud.d.ts +4 -3
- package/dist/antifraud.esm.js +7 -6
- package/dist/{antifraudManager-DZifA5zX.esm.js → antifraudManager-BKdQoMFf.esm.js} +4 -3
- package/dist/{antifraudManager-B7WQTIdu.d.ts → antifraudManager-DpIvbrZP.d.ts} +2 -2
- package/dist/{antifraudStateMachine-DlKrK2hL.esm.js → antifraudStateMachine-C0NGnP9W.esm.js} +3 -3
- package/dist/{api-DUZwyz0o.esm.js → api-DsbUi7TO.esm.js} +21 -2
- package/dist/authentication.d.ts +19 -12
- package/dist/authentication.esm.js +33 -27
- package/dist/{authenticationManager-Dve7mGLe.d.ts → authenticationManager-CdPbNS2H.d.ts} +3 -3
- package/dist/{authenticationManager-BM4LzyV7.esm.js → authenticationManager-Dtl1vcET.esm.js} +6 -6
- package/dist/{authenticationStateMachine-Chx3ySdT.esm.js → authenticationStateMachine-D-hOlg8p.esm.js} +8 -8
- package/dist/avatar.d.ts +3 -0
- package/dist/avatar.esm.js +3 -0
- package/dist/{backCameraStream-iftkf3rd.esm.js → backCameraStream-ZyqrFyjO.esm.js} +3 -3
- package/dist/{browser-DlFgHpoC-7tbRR1aR.esm.js → browser-DlFgHpoC-qLKs75Qp.esm.js} +1 -1
- package/dist/{browserSimulation-CGjpdNA1.esm.js → browserSimulation-TsSoavQv.esm.js} +153 -1216
- package/dist/camera-DUkgmYMo.esm.js +79 -0
- package/dist/camera.d.ts +7 -1
- package/dist/camera.esm.js +4 -4
- package/dist/{StreamCanvasCapture-Ctgyx-oD.d.ts → canvas-CJUmoXSD.d.ts} +1 -41
- package/dist/certificate-issuance.d.ts +31 -30
- package/dist/certificate-issuance.esm.js +8 -7
- package/dist/{certificateIssuanceStateMachine-CLNXtXaC.esm.js → certificateIssuanceStateMachine-C07m7qtP.esm.js} +4 -4
- package/dist/clientLifecycle-Ct7h9uxx.esm.js +534 -0
- package/dist/consent.d.ts +4 -3
- package/dist/consent.esm.js +7 -6
- package/dist/{consentManager-psDCeJ0E.esm.js → consentManager-BtnPZUW4.esm.js} +4 -3
- package/dist/{consentManager-BqUGU_YD.d.ts → consentManager-DufVo9D9.d.ts} +52 -52
- package/dist/{consentStateMachine-DRsFFSHE.esm.js → consentStateMachine-DO2JqoZW.esm.js} +3 -3
- package/dist/cpf-ocr.d.ts +4 -3
- package/dist/cpf-ocr.esm.js +7 -6
- package/dist/{cpfOcrManager-BxcgxvgO.esm.js → cpfOcrManager-C4rLgKsu.esm.js} +6 -5
- package/dist/{cpfOcrManager-B2u9hMzd.d.ts → cpfOcrManager-DqZZ94Gt.d.ts} +19 -19
- package/dist/createFaceAvatar-B8pFpy6o.d.ts +401 -0
- package/dist/createFaceAvatar-BOHDKQRq.esm.js +46 -0
- package/dist/cross-document-data-match.d.ts +2 -1
- package/dist/cross-document-data-match.esm.js +6 -5
- package/dist/curp-validation.d.ts +10 -7
- package/dist/curp-validation.esm.js +7 -6
- package/dist/{curpValidationManager-BgLDw0Ub.esm.js → curpValidationManager-BafobPXC.esm.js} +4 -3
- package/dist/{curpValidationManager-Bt6CVD8b.d.ts → curpValidationManager-Co6LZAHT.d.ts} +3 -3
- package/dist/{curpValidationStateMachine-BK_wJ48C.esm.js → curpValidationStateMachine-DXTPK6vI.esm.js} +3 -3
- package/dist/custom-fields.d.ts +3 -2
- package/dist/custom-fields.esm.js +6 -5
- package/dist/custom-watchlist.d.ts +2 -1
- package/dist/custom-watchlist.esm.js +6 -5
- package/dist/{customWatchlistStateMachine-Bi4P59HM.esm.js → customWatchlistStateMachine-B4G3Chkx.esm.js} +3 -3
- package/dist/{deepsightLoader-BMmvk0X6.esm.js → deepsightLoader-DHSVD776.esm.js} +14 -13
- package/dist/{deepsightService-CEZrdiW0.esm.js → deepsightService-CDahSjGP.esm.js} +9 -38
- package/dist/deepsightService-CMB9XjES.d.ts +69 -0
- package/dist/device.d.ts +9 -1
- package/dist/device.esm.js +5 -5
- package/dist/document-capture.d.ts +175 -174
- package/dist/document-capture.esm.js +14 -13
- package/dist/document-upload.d.ts +48 -47
- package/dist/document-upload.esm.js +9 -8
- package/dist/{documentCaptureStateMachine-MpwpJO7t.esm.js → documentCaptureStateMachine-DSOX2epf.esm.js} +7 -7
- package/dist/dynamic-forms.d.ts +4 -192
- package/dist/dynamic-forms.esm.js +8 -160
- package/dist/dynamicFormsManager-BOefZbyF.esm.js +168 -0
- package/dist/dynamicFormsManager-CUYLOj9g.d.ts +239 -0
- package/dist/{dynamicFormsStateMachine-Do84A4uU.esm.js → dynamicFormsStateMachine-BXHcaocI.esm.js} +4 -4
- package/dist/ekyb.d.ts +9 -6
- package/dist/ekyb.esm.js +20 -17
- package/dist/{ekybStateMachine-nsuPOw3f.esm.js → ekybStateMachine-CCSeqjCt.esm.js} +5 -5
- package/dist/ekyc.d.ts +4 -3
- package/dist/ekyc.esm.js +9 -8
- package/dist/{ekycStateMachine-DcynX0rj.esm.js → ekycStateMachine-DkLVTOyA.esm.js} +5 -5
- package/dist/electronic-signature.d.ts +4 -3
- package/dist/electronic-signature.esm.js +9 -8
- package/dist/{electronicSignatureManager-B63aAuI5.esm.js → electronicSignatureManager-DjzUzqZj.esm.js} +50 -87
- package/dist/email.d.ts +5 -4
- package/dist/email.esm.js +9 -8
- package/dist/{emailManager-DxHjAe5u.esm.js → emailManager-B_XzEsXH.esm.js} +5 -4
- package/dist/{emailManager-C6D2u4mQ.d.ts → emailManager-fHASH6uo.d.ts} +3 -3
- package/dist/{emailStateMachine-758f34gg.esm.js → emailStateMachine-fqJ42b19.esm.js} +5 -5
- package/dist/{endpoints-BM2_j_IE.esm.js → endpoints-BzMJFAPV.esm.js} +13 -0
- package/dist/{events-DN-u67gb.esm.js → events-CqGY1yMq.esm.js} +2 -188
- package/dist/events.d.ts +2 -0
- package/dist/events.esm.js +3 -2
- package/dist/extensibility.d.ts +37 -26
- package/dist/extensibility.esm.js +74 -59
- package/dist/{externalFetch-crimxkN9.esm.js → externalFetch-CyhBfD-n.esm.js} +1 -1
- package/dist/face-match.d.ts +2 -2
- package/dist/face-match.esm.js +6 -5
- package/dist/{faceCaptureManagerFactory-BNXZchWX.esm.js → faceCaptureManagerFactory-2brKTzMb.esm.js} +25 -16
- package/dist/faceCaptureManagerFactory-CGKbf7zt.d.ts +400 -0
- package/dist/{faceCaptureSetup-Dqb09D24.esm.js → faceCaptureSetup-CDUVU1nl.esm.js} +97 -13
- package/dist/{faceMatchStateMachine-FikRaUZH.esm.js → faceMatchStateMachine-DUeUjEPx.esm.js} +3 -3
- package/dist/field-comparison.d.ts +4 -3
- package/dist/field-comparison.esm.js +6 -5
- package/dist/{fieldComparisonManager-Dic0DVgl.esm.js → fieldComparisonManager-B9W0a9A5.esm.js} +5 -4
- package/dist/{fieldComparisonManager-BXc6-DJv.d.ts → fieldComparisonManager-DdLqDeB2.d.ts} +2 -2
- package/dist/fiscal-qr.d.ts +5 -3
- package/dist/fiscal-qr.esm.js +13 -12
- package/dist/flow-events.d.ts +8 -6
- package/dist/flow.d.ts +21 -13
- package/dist/flow.esm.js +28 -19
- package/dist/{flowCompletionService-CdGDKv0z.d.ts → flowCompletionService-BIqSTZcd.d.ts} +2 -1
- package/dist/{flowCompletionService-D_CsiDZM.esm.js → flowCompletionService-qEqh_g14.esm.js} +3 -3
- package/dist/{flowServices-ar5DXv71.esm.js → flowServices-C4piblgx.esm.js} +24 -9
- package/dist/geolocation.d.ts +4 -127
- package/dist/geolocation.esm.js +8 -115
- package/dist/geolocationManager-BP2Do9O-.esm.js +123 -0
- package/dist/geolocationManager-Cs50eRlC.d.ts +156 -0
- package/dist/{geolocationStateMachine-BWjrMGS_.esm.js → geolocationStateMachine-BYfOOOG0.esm.js} +4 -4
- package/dist/{getDeviceClass-rXKlXywy.esm.js → getDeviceClass-DGGzzfqm.esm.js} +1 -1
- package/dist/getDeviceData-TrKM8NPh.esm.js +101 -0
- package/dist/government-validation.d.ts +4 -3
- package/dist/government-validation.esm.js +8 -7
- package/dist/{governmentValidationStateMachine-BoF7U1of.esm.js → governmentValidationStateMachine-CwoPPO8b.esm.js} +5 -5
- package/dist/home.d.ts +18 -15
- package/dist/home.esm.js +4 -3
- package/dist/http.esm.js +2 -2
- package/dist/id-ocr.d.ts +4 -625
- package/dist/id-ocr.esm.js +8 -84
- package/dist/id-verification.d.ts +28 -27
- package/dist/id-verification.esm.js +6 -5
- package/dist/id.d.ts +16 -11
- package/dist/id.esm.js +39 -32
- package/dist/idCaptureDefaults-DNJON8Xo.esm.js +188 -0
- package/dist/{idCaptureManager-BvGUSN2e.d.ts → idCaptureManager-BVdvfJys.d.ts} +40 -10
- package/dist/{idCaptureManager-Bm6Wm6Qv.esm.js → idCaptureManager-Bo9DCQSU.esm.js} +14 -11
- package/dist/{idCaptureStateMachine-D1YhOj6n.esm.js → idCaptureStateMachine-BeDidMLU.esm.js} +395 -382
- package/dist/idOcrManager-BkRDrpZu.esm.js +92 -0
- package/dist/idOcrManager-CJzrmM91.d.ts +641 -0
- package/dist/{idOcrStateMachine-YMCDGplq.esm.js → idOcrStateMachine-DRZc4FBl.esm.js} +5 -5
- package/dist/{idVerificationStateMachine-Bo1IYuZH.esm.js → idVerificationStateMachine-oqiERKpg.esm.js} +3 -3
- package/dist/identity-reuse.d.ts +4 -3
- package/dist/identity-reuse.esm.js +7 -6
- package/dist/{identityReuseManager-D0knIrOy.esm.js → identityReuseManager-Bu62LToE.esm.js} +4 -3
- package/dist/{identityReuseManager-DcWSvC-A.d.ts → identityReuseManager-TWKbXuvY.d.ts} +33 -33
- package/dist/{identityReuseStateMachine-CSQfZOqD.esm.js → identityReuseStateMachine-BmONeF6B.esm.js} +3 -3
- package/dist/{index-BgxYUYuD.d.ts → index-DtUlD-bz.d.ts} +2 -2
- package/dist/{index-DV134v62.d.ts → index-Icld-cKW.d.ts} +175 -295
- package/dist/index.d.ts +3 -3
- package/dist/index.esm.js +16 -13
- package/dist/{invokeOnCaptureCallback-Dzemudhk.esm.js → invokeOnCaptureCallback-v_PV_IJz.esm.js} +1 -1
- package/dist/{lib-7ZQ9BdKH.esm.js → lib-B050WTth.esm.js} +1 -1
- package/dist/mandatory-consent.d.ts +10 -7
- package/dist/mandatory-consent.esm.js +7 -6
- package/dist/{mandatoryConsentManager-Tzl5Qd35.d.ts → mandatoryConsentManager-Bmdjo14S.d.ts} +50 -50
- package/dist/{mandatoryConsentManager-Lon46nPQ.esm.js → mandatoryConsentManager-CQ3jirlZ.esm.js} +4 -3
- package/dist/{mandatoryConsentStateMachine-D_dZ7IF6.esm.js → mandatoryConsentStateMachine-DkJJLHqC.esm.js} +3 -3
- package/dist/mediaRecorderOptions-C3NAkVgK.esm.js +42 -0
- package/dist/mediapipeShared-CRAKVElv.esm.js +67 -0
- package/dist/{recordingService-CxMJP4tQ.esm.js → onDeviceFaceResults-DVBggeq8.esm.js} +71 -389
- package/dist/{openviduLazy-oAXXI9w_.esm.js → openviduLazy-BTOIauGP.esm.js} +2 -2
- package/dist/openviduLazy-Dov8oKfL.esm.js +3 -0
- package/dist/{permissionGuards-D-Z-lgNj.esm.js → permissionGuards-BR2oN573.esm.js} +1 -1
- package/dist/{permissionServices-CPDboRoE.esm.js → permissionServices-BJhGL13R.esm.js} +7 -4
- package/dist/personhood.d.ts +534 -0
- package/dist/personhood.esm.js +1494 -0
- package/dist/phone.d.ts +5 -4
- package/dist/phone.esm.js +9 -8
- package/dist/{phoneManager-TSq1Ief6.d.ts → phoneManager-BOTcjXgs.d.ts} +3 -3
- package/dist/{phoneManager-Blt4Oc2P.esm.js → phoneManager-BWlxqbyD.esm.js} +5 -4
- package/dist/{phoneStateMachine-W3c6Z8sj.esm.js → phoneStateMachine-B_V9nODs.esm.js} +5 -5
- package/dist/{qe-signature-CQLK6LKE.esm.js → qe-signature-BhFnI9pT.esm.js} +1 -1
- package/dist/qe-signature.d.ts +4 -3
- package/dist/qe-signature.esm.js +10 -9
- package/dist/recordingService-m_1xCthr.esm.js +347 -0
- package/dist/redirect-to-mobile.d.ts +4 -3
- package/dist/redirect-to-mobile.esm.js +9 -8
- package/dist/{redirectToMobileManager-DCvpKdkD.esm.js → redirectToMobileManager-BaAo9rgi.esm.js} +5 -4
- package/dist/{redirectToMobileManager-yOHAmwg-.d.ts → redirectToMobileManager-Br0KZK1G.d.ts} +2 -2
- package/dist/{redirectToMobileStateMachine-DC9z4Vfu.esm.js → redirectToMobileStateMachine-DoAnf2ql.esm.js} +10 -12
- package/dist/{runChildModule-C9G0wfVC.esm.js → runChildModule-0nQfjVVo.esm.js} +3 -3
- package/dist/selfie.d.ts +19 -12
- package/dist/selfie.esm.js +34 -28
- package/dist/{selfieManager-BRL6c_Oc.esm.js → selfieManager-D_ZFERsE.esm.js} +6 -6
- package/dist/{selfieManager-imY2dAcH.d.ts → selfieManager-R0Jw5sjb.d.ts} +3 -3
- package/dist/{selfieStateMachine-D2Q0uwDK.esm.js → selfieStateMachine-CIgeeiN9.esm.js} +4 -4
- package/dist/{session-Be_IA1e0.esm.js → session-9jIfKFCc.esm.js} +3 -3
- package/dist/session.d.ts +7 -5
- package/dist/session.esm.js +16 -14
- package/dist/{sessionInitializer-CaiGA8GP.esm.js → sessionInitializer-C_TYIgyk.esm.js} +11 -107
- package/dist/{setup-BJZAkajt.d.ts → setup-CpRnXxn8.d.ts} +16 -3
- package/dist/setup-DHc6OaHm.esm.js +562 -0
- package/dist/signature.d.ts +3 -2
- package/dist/signature.esm.js +6 -5
- package/dist/{signatureStateMachine-DAxXmMrk.esm.js → signatureStateMachine-DtoiPzys.esm.js} +3 -3
- package/dist/{stats-DntyIiGr.esm.js → stats-CYcScXcO.esm.js} +2 -2
- package/dist/stats.esm.js +3 -3
- package/dist/thumbmarkAdapter-fHBUDlAP.esm.js +1194 -0
- package/dist/{tri-CtxWthxz.esm.js → tri-BG1iJsr4.esm.js} +12 -2
- package/dist/tri.d.ts +1 -1
- package/dist/{tri.esm-CrWAulDi.esm.js → tri.esm-DUC01NgO.esm.js} +27 -23
- package/dist/tri.esm.js +2 -2
- package/dist/trust-graph.d.ts +4 -3
- package/dist/trust-graph.esm.js +4 -3
- package/dist/{types-DpTWgfKY.d.ts → types-BklZ_Wne.d.ts} +1 -1
- package/dist/{types-Dv-9WPJX.d.ts → types-BsYXnC5s.d.ts} +39 -3
- package/dist/types-CxW2e0Ar.d.ts +132 -0
- package/dist/{types-Z9wv19v2.d.ts → types-DgGlgyZT.d.ts} +1 -1
- package/dist/types-ElOJAGaM.esm.js +189 -0
- package/dist/video-C2WIj6TS.esm.js +36 -0
- package/dist/video-selfie.d.ts +351 -0
- package/dist/video-selfie.esm.js +1509 -0
- package/dist/videoSelfieSteps-7M5vxKn-.esm.js +58 -0
- package/dist/{warmup--6HA3yUR.d.ts → warmup-BJZPQIiX.d.ts} +5 -1
- package/dist/wasm.d.ts +4 -4
- package/dist/wasm.esm.js +17 -14
- package/dist/watchlist-for-business.d.ts +2 -1
- package/dist/watchlist-for-business.esm.js +6 -5
- package/dist/watchlist.d.ts +2 -1
- package/dist/watchlist.esm.js +6 -5
- package/dist/{watchlistForBusinessStateMachine-DtefJb-D.esm.js → watchlistForBusinessStateMachine-DvEunDxs.esm.js} +3 -3
- package/dist/{watchlistStateMachine-B0aIS2_n.esm.js → watchlistStateMachine-AHXVf-TB.esm.js} +3 -3
- package/dist/workflow.d.ts +230 -98
- package/dist/workflow.esm.js +123 -79
- package/dist/{xstate.esm-Dkm1jy5u.esm.js → xstate.esm-Bomq1a0z.esm.js} +41 -1
- package/package.json +16 -1
- package/dist/IpifyProvider-oZf7Ke1O.esm.js +0 -167
- package/dist/camera-x4LjGTjZ.esm.js +0 -22
- package/dist/deepsightService-ByNKY5Jv.d.ts +0 -276
- package/dist/faceCaptureManagerFactory-CL-7T_vH.d.ts +0 -731
- package/dist/getBrowser-Dv8LZyHE.esm.js +0 -8
- package/dist/openviduLazy-B_s6jnEk.esm.js +0 -3
- package/dist/setup-C7tintPc.esm.js +0 -1064
- /package/dist/{Actor-fIPLXfoi.d.ts → Actor-CGEelYtQ.d.ts} +0 -0
- /package/dist/{BrowserTimerProvider-BVgutW_b.esm.js → BrowserTimerProvider-CHloo0eQ.esm.js} +0 -0
- /package/dist/{ITimerCapability-BvH0pDwp.esm.js → ITimerCapability-pltuG1E8.esm.js} +0 -0
- /package/dist/{MotionSensorProvider-C9fPAJdS.esm.js → MotionSensorProvider-DCbId4De.esm.js} +0 -0
- /package/dist/{apiError-D-GTvFrH.esm.js → apiError-DNS2OCEX.esm.js} +0 -0
- /package/dist/{camera-biGPWlmx.d.ts → camera-CNVUo-bC.d.ts} +0 -0
- /package/dist/{canvas-7uX2izHb.esm.js → canvas-_I95hkPg.esm.js} +0 -0
- /package/dist/{chunk-BK1xu56N.esm.js → chunk-CIKcdghV.esm.js} +0 -0
- /package/dist/{cpf-xBg-DFNn.esm.js → cpf-BKaho0j_.esm.js} +0 -0
- /package/dist/{dateUtils-8dCcuArE.esm.js → dateUtils-7-tQYRNN.esm.js} +0 -0
- /package/dist/{getDeviceClass-C0zrM97a.esm.js → getDeviceClass-DavNdcxh.esm.js} +0 -0
- /package/dist/{http-CfVTgMfU.esm.js → http-BkiUT7EB.esm.js} +0 -0
- /package/dist/{index-52_qi4bd.d.ts → index-XTtxaahe.d.ts} +0 -0
- /package/dist/{otp-D87RTwrx.esm.js → otp-Bfnzf7I3.esm.js} +0 -0
- /package/dist/{otp-BHJnjEEc.d.ts → otp-M4iy57XY.d.ts} +0 -0
- /package/dist/{platform-BfoqeM61.esm.js → platform-uIVQUnJP.esm.js} +0 -0
- /package/dist/{types-BdVOWGGW.esm.js → types-CMcxlSmH.esm.js} +0 -0
- /package/dist/{types-DYZja-k9.d.ts → types-D_IceWxT.d.ts} +0 -0
- /package/dist/{types-F0IXbaCB.d.ts → types-Duy2rDxF.d.ts} +0 -0
- /package/dist/{urlUtils-8RNHyW3x.esm.js → urlUtils-BZqFU9rR.esm.js} +0 -0
|
@@ -0,0 +1,1509 @@
|
|
|
1
|
+
import { t as WasmUtilProvider } from "./WasmUtilProvider-ADG54T6M.esm.js";
|
|
2
|
+
import { t as ensureWasmPipelines } from "./setup-DHc6OaHm.esm.js";
|
|
3
|
+
import { t as BrowserTimerProvider } from "./BrowserTimerProvider-CHloo0eQ.esm.js";
|
|
4
|
+
import { t as api } from "./api-DsbUi7TO.esm.js";
|
|
5
|
+
import "./clientLifecycle-Ct7h9uxx.esm.js";
|
|
6
|
+
import "./events-CqGY1yMq.esm.js";
|
|
7
|
+
import "./types-ElOJAGaM.esm.js";
|
|
8
|
+
import { t as endpoints } from "./endpoints-BzMJFAPV.esm.js";
|
|
9
|
+
import "./stats-CYcScXcO.esm.js";
|
|
10
|
+
import "./browserSimulation-TsSoavQv.esm.js";
|
|
11
|
+
import "./sessionInitializer-C_TYIgyk.esm.js";
|
|
12
|
+
import "./getDeviceData-TrKM8NPh.esm.js";
|
|
13
|
+
import "./thumbmarkAdapter-fHBUDlAP.esm.js";
|
|
14
|
+
import "./IpifyProvider-BRW14_uo.esm.js";
|
|
15
|
+
import "./platform-uIVQUnJP.esm.js";
|
|
16
|
+
import "./tri-BG1iJsr4.esm.js";
|
|
17
|
+
import { a as fromPromise, i as fromCallback, l as createManager, r as assign, s as createActor, t as setup } from "./xstate.esm-Bomq1a0z.esm.js";
|
|
18
|
+
import "./MotionSensorProvider-DCbId4De.esm.js";
|
|
19
|
+
import { t as checkPermission } from "./permissionServices-BJhGL13R.esm.js";
|
|
20
|
+
import { i as requestCameraWithFallback } from "./camera-DUkgmYMo.esm.js";
|
|
21
|
+
import { a as encryptSelfieImage, b as FaceDetectionProvider, l as startDetection, m as uploadSelfie, p as processFace, y as StreamCanvasProcessingSession } from "./onDeviceFaceResults-DVBggeq8.esm.js";
|
|
22
|
+
import "./ITimerCapability-pltuG1E8.esm.js";
|
|
23
|
+
import "./canvas-_I95hkPg.esm.js";
|
|
24
|
+
import "./backCameraStream-ZyqrFyjO.esm.js";
|
|
25
|
+
import "./video-C2WIj6TS.esm.js";
|
|
26
|
+
import { n as getWindowDimensions } from "./getDeviceClass-DavNdcxh.esm.js";
|
|
27
|
+
import "./deepsightService-CDahSjGP.esm.js";
|
|
28
|
+
import "./getDeviceClass-DGGzzfqm.esm.js";
|
|
29
|
+
import "./types-CMcxlSmH.esm.js";
|
|
30
|
+
import { t as StreamCanvasCapture } from "./StreamCanvasCapture-Bz17q4FP.esm.js";
|
|
31
|
+
import { n as getSupportedMediaRecorderMimeType } from "./mediaRecorderOptions-C3NAkVgK.esm.js";
|
|
32
|
+
import { n as resolveVideoSelfieSteps, r as videoSelfieWasmPipelines, t as facingModeForStep } from "./videoSelfieSteps-7M5vxKn-.esm.js";
|
|
33
|
+
import { i as IdCaptureProvider, n as DEFAULT_ID_CAPTURE_MODEL_VERSION, r as DEFAULT_ID_CAPTURE_THRESHOLDS, t as DEFAULT_ID_CAPTURE_GEOMETRY } from "./idCaptureDefaults-DNJON8Xo.esm.js";
|
|
34
|
+
|
|
35
|
+
//#region src/modules/video-selfie/videoSelfieCompareService.ts
|
|
36
|
+
async function postVideoSelfieCompare(endpoint, base64Image) {
|
|
37
|
+
return (await api.post(endpoint, { base64Image })).data;
|
|
38
|
+
}
|
|
39
|
+
/** Compare a front-ID frame from the video selfie against the captured ID. */
|
|
40
|
+
const compareVideoSelfieId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareId, base64Image);
|
|
41
|
+
/** Compare OCR from a front-ID frame against the captured ID's OCR. */
|
|
42
|
+
const compareVideoSelfieOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareOcr, base64Image);
|
|
43
|
+
/** Compare a back-ID frame from the video selfie against the captured ID. */
|
|
44
|
+
const compareVideoSelfieBackId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackId, base64Image);
|
|
45
|
+
/** Compare OCR from a back-ID frame against the captured ID's OCR. */
|
|
46
|
+
const compareVideoSelfieBackOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackOcr, base64Image);
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region src/modules/video-selfie/videoSelfieStepDetector.ts
|
|
50
|
+
/** Default autocapture window for the selfie step when the config omits one. */
|
|
51
|
+
const SELFIE_AUTOCAPTURE_MS = 1e4;
|
|
52
|
+
/**
|
|
53
|
+
* Whether the backend reported a confident face match on a front-ID compare.
|
|
54
|
+
* The backend normalizes `faceRecognitionConfidence` to 0 (no match) or 1 (match).
|
|
55
|
+
*/
|
|
56
|
+
function isFaceRecognitionMatch(confidence) {
|
|
57
|
+
if (typeof confidence !== "number") return true;
|
|
58
|
+
return confidence >= 1;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Max selfie capture attempts before the step fails. Each backend rejection
|
|
62
|
+
* (e.g. 4077 low quality / liveness) re-arms the pipeline and scans again;
|
|
63
|
+
* after this many tries the step rejects. Mirrors V1's `numberOfTries`.
|
|
64
|
+
*/
|
|
65
|
+
const MAX_SELFIE_ATTEMPTS = 3;
|
|
66
|
+
/**
|
|
67
|
+
* How long the ID step holds on the "success" state (green check) after the
|
|
68
|
+
* backend validates a side, before advancing. Gives the user a beat to see the
|
|
69
|
+
* capture succeeded instead of the step jumping silently. Mirrors V1's
|
|
70
|
+
* IdVideoSelfie success flash.
|
|
71
|
+
*/
|
|
72
|
+
const ID_SUCCESS_DWELL_MS = 900;
|
|
73
|
+
/**
|
|
74
|
+
* How long an ID step holds the "show the front / back of your ID" instruction
|
|
75
|
+
* before autocapture is armed. No frame is captured or validated during this
|
|
76
|
+
* window, so the guidance can't be overwritten by capture feedback before the
|
|
77
|
+
* user has read it. Mirrors the UI's `ID_INSTRUCTION_DWELL_MS` message pin.
|
|
78
|
+
*/
|
|
79
|
+
const ID_INSTRUCTION_DWELL_MS = 3e3;
|
|
80
|
+
/** Resolves after `ms`, or rejects early if `signal` aborts. */
|
|
81
|
+
function delay(ms, signal) {
|
|
82
|
+
const timerProvider = BrowserTimerProvider.getInstance();
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const timer = timerProvider.setTimeout(() => {
|
|
85
|
+
signal.removeEventListener("abort", onAbort);
|
|
86
|
+
resolve();
|
|
87
|
+
}, ms);
|
|
88
|
+
const onAbort = () => {
|
|
89
|
+
timerProvider.clearTimeout(timer);
|
|
90
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
91
|
+
};
|
|
92
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Builds the default per-step detector from the module config. Today it
|
|
97
|
+
* auto-captures the `selfie` step (face detection → `add/face` → `process/face`);
|
|
98
|
+
* ID / proof-of-address / questions / speech advance manually until their
|
|
99
|
+
* detectors land.
|
|
100
|
+
*/
|
|
101
|
+
function createDefaultStepDetector(config, deps) {
|
|
102
|
+
return ({ step, stream, signal, onStatus }) => {
|
|
103
|
+
if (step === "selfie") return detectSelfie({
|
|
104
|
+
config,
|
|
105
|
+
deps,
|
|
106
|
+
stream,
|
|
107
|
+
signal,
|
|
108
|
+
onStatus
|
|
109
|
+
});
|
|
110
|
+
if (step === "frontId" || step === "backId") return detectId({
|
|
111
|
+
step,
|
|
112
|
+
config,
|
|
113
|
+
stream,
|
|
114
|
+
signal,
|
|
115
|
+
onStatus
|
|
116
|
+
});
|
|
117
|
+
return new Promise(() => {});
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function detectSelfie(params) {
|
|
121
|
+
const { config, deps, stream, signal, onStatus } = params;
|
|
122
|
+
const provider = new FaceDetectionProvider();
|
|
123
|
+
await provider.initialize({
|
|
124
|
+
autocaptureInterval: SELFIE_AUTOCAPTURE_MS,
|
|
125
|
+
useOnDeviceWorkflow: false,
|
|
126
|
+
videoSelfie: true
|
|
127
|
+
});
|
|
128
|
+
provider.setChecksEnabled({
|
|
129
|
+
lenses: config.validateLenses ?? false,
|
|
130
|
+
mask: config.validateFaceMask ?? false,
|
|
131
|
+
closedEyes: config.validateClosedEyes ?? false,
|
|
132
|
+
headWear: config.validateHeadCover ?? false,
|
|
133
|
+
occlusion: false
|
|
134
|
+
});
|
|
135
|
+
const imageType = config.useAsSelfie === true ? "selfie" : "videoSelfie";
|
|
136
|
+
const capturer = new StreamCanvasCapture(stream);
|
|
137
|
+
const detectionConfig = {
|
|
138
|
+
showTutorial: false,
|
|
139
|
+
validateBrightness: false,
|
|
140
|
+
validateLenses: config.validateLenses ?? false,
|
|
141
|
+
validateFaceMask: config.validateFaceMask ?? false,
|
|
142
|
+
validateClosedEyes: config.validateClosedEyes ?? false,
|
|
143
|
+
validateHeadCover: config.validateHeadCover ?? false,
|
|
144
|
+
autoCaptureTimeout: SELFIE_AUTOCAPTURE_MS / 1e3
|
|
145
|
+
};
|
|
146
|
+
const runAttempt = () => new Promise((resolve, reject) => {
|
|
147
|
+
const onAbort = () => {
|
|
148
|
+
detection.cleanup();
|
|
149
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
150
|
+
};
|
|
151
|
+
const settle = (run) => {
|
|
152
|
+
signal.removeEventListener("abort", onAbort);
|
|
153
|
+
run();
|
|
154
|
+
};
|
|
155
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
156
|
+
const detection = startDetection({
|
|
157
|
+
config: detectionConfig,
|
|
158
|
+
capturer,
|
|
159
|
+
provider,
|
|
160
|
+
onUpdate: (status) => onStatus?.(status),
|
|
161
|
+
onSuccess: (canvas, faceCoordinates) => {
|
|
162
|
+
(async () => {
|
|
163
|
+
try {
|
|
164
|
+
await uploadSelfie({
|
|
165
|
+
encryptedBase64Image: await encryptSelfieImage({
|
|
166
|
+
canvas,
|
|
167
|
+
dependencies: { getWasmUtil: async () => deps.getWasmUtil() }
|
|
168
|
+
}),
|
|
169
|
+
faceCoordinates,
|
|
170
|
+
imageType
|
|
171
|
+
});
|
|
172
|
+
await processFace(imageType);
|
|
173
|
+
settle(resolve);
|
|
174
|
+
} catch (error$1) {
|
|
175
|
+
settle(() => reject(error$1 instanceof Error ? error$1 : new Error(String(error$1))));
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
try {
|
|
182
|
+
for (let attempt = 1; attempt <= MAX_SELFIE_ATTEMPTS; attempt++) try {
|
|
183
|
+
await runAttempt();
|
|
184
|
+
return;
|
|
185
|
+
} catch (error$1) {
|
|
186
|
+
if (signal.aborted || attempt >= MAX_SELFIE_ATTEMPTS) throw error$1;
|
|
187
|
+
onStatus?.("error");
|
|
188
|
+
provider.reset();
|
|
189
|
+
await delay(1500, signal);
|
|
190
|
+
}
|
|
191
|
+
} finally {
|
|
192
|
+
capturer.dispose();
|
|
193
|
+
provider.dispose();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
async function detectId(params) {
|
|
197
|
+
const { step, config, stream, signal, onStatus } = params;
|
|
198
|
+
const provider = new IdCaptureProvider();
|
|
199
|
+
await provider.initialize({ videoSelfie: true });
|
|
200
|
+
const windowDimensions = getWindowDimensions();
|
|
201
|
+
provider.setGeometry({
|
|
202
|
+
...DEFAULT_ID_CAPTURE_GEOMETRY,
|
|
203
|
+
windowOuterWidth: windowDimensions.outerWidth,
|
|
204
|
+
windowOuterHeight: windowDimensions.outerHeight,
|
|
205
|
+
windowInnerWidth: windowDimensions.innerWidth,
|
|
206
|
+
windowInnerHeight: windowDimensions.innerHeight
|
|
207
|
+
});
|
|
208
|
+
provider.setSettings({
|
|
209
|
+
isFixedMask: false,
|
|
210
|
+
isIPhone14OrHigher: false,
|
|
211
|
+
idType: step === "backId" ? "BackId" : "FrontId",
|
|
212
|
+
blurCheckEnabled: true,
|
|
213
|
+
glareCheckEnabled: false,
|
|
214
|
+
faceQualityCheckEnabled: false,
|
|
215
|
+
iouCheckEnabled: true,
|
|
216
|
+
fpsLimitEnabled: false
|
|
217
|
+
});
|
|
218
|
+
provider.setThresholds(DEFAULT_ID_CAPTURE_THRESHOLDS);
|
|
219
|
+
provider.setModelType(DEFAULT_ID_CAPTURE_MODEL_VERSION);
|
|
220
|
+
const capturer = new StreamCanvasCapture(stream);
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
let validating = false;
|
|
223
|
+
let armed = false;
|
|
224
|
+
provider.setCallbacks({
|
|
225
|
+
onFarAway: () => {
|
|
226
|
+
if (armed && !validating) onStatus?.("tooFar");
|
|
227
|
+
},
|
|
228
|
+
onBlur: () => {
|
|
229
|
+
if (armed && !validating) onStatus?.("blur");
|
|
230
|
+
},
|
|
231
|
+
onCapturing: () => {
|
|
232
|
+
if (!armed || validating) return;
|
|
233
|
+
const base64Image = capturer.getLatestCanvas()?.getBase64Image();
|
|
234
|
+
if (!base64Image) return;
|
|
235
|
+
validating = true;
|
|
236
|
+
onStatus?.("capturing");
|
|
237
|
+
(async () => {
|
|
238
|
+
try {
|
|
239
|
+
if (await validateIdFrame(step, config, base64Image)) {
|
|
240
|
+
onStatus?.("success");
|
|
241
|
+
await delay(ID_SUCCESS_DWELL_MS, signal);
|
|
242
|
+
teardown();
|
|
243
|
+
resolve();
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
validating = false;
|
|
247
|
+
} catch (error$1) {
|
|
248
|
+
teardown();
|
|
249
|
+
reject(error$1 instanceof Error ? error$1 : new Error(String(error$1)));
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
const session = new StreamCanvasProcessingSession({
|
|
255
|
+
capturer,
|
|
256
|
+
provider
|
|
257
|
+
});
|
|
258
|
+
let toreDown = false;
|
|
259
|
+
const teardown = () => {
|
|
260
|
+
if (toreDown) return;
|
|
261
|
+
toreDown = true;
|
|
262
|
+
session.dispose();
|
|
263
|
+
capturer.dispose();
|
|
264
|
+
provider.dispose();
|
|
265
|
+
};
|
|
266
|
+
signal.addEventListener("abort", () => {
|
|
267
|
+
teardown();
|
|
268
|
+
reject(/* @__PURE__ */ new Error("aborted"));
|
|
269
|
+
}, { once: true });
|
|
270
|
+
delay(ID_INSTRUCTION_DWELL_MS, signal).then(() => {
|
|
271
|
+
if (toreDown || signal.aborted) return;
|
|
272
|
+
armed = true;
|
|
273
|
+
onStatus?.("detecting");
|
|
274
|
+
}).catch(() => {});
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
/** Runs the configured ID/OCR comparisons for a captured ID frame. */
|
|
278
|
+
async function validateIdFrame(step, config, base64Image) {
|
|
279
|
+
if (step === "backId") {
|
|
280
|
+
if (config.compareBackIdEnabled) {
|
|
281
|
+
const result = await compareVideoSelfieBackId(base64Image);
|
|
282
|
+
if (result.idSideMatched === false || result.success === false) return false;
|
|
283
|
+
}
|
|
284
|
+
if (config.compareBackOcrEnabled) {
|
|
285
|
+
if ((await compareVideoSelfieBackOcr(base64Image)).success === false) return false;
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (config.compareIdEnabled) {
|
|
290
|
+
const result = await compareVideoSelfieId(base64Image);
|
|
291
|
+
if (result.idTypeMatched === false) return false;
|
|
292
|
+
if (typeof result.faceRecognitionConfidence === "number" && !isFaceRecognitionMatch(result.faceRecognitionConfidence)) return false;
|
|
293
|
+
}
|
|
294
|
+
if (config.compareOcrEnabled) {
|
|
295
|
+
if ((await compareVideoSelfieOcr(base64Image)).success === false) return false;
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region ../infra/src/media/CameraCompositor.ts
|
|
302
|
+
/**
|
|
303
|
+
* Composites the *active* camera into a canvas and exposes the canvas as a
|
|
304
|
+
* single, stable `MediaStream` to record. Switching cameras (front ↔ back)
|
|
305
|
+
* just repoints what the canvas draws — the output stream and its audio track
|
|
306
|
+
* are never replaced — so a single `MediaRecorder` produces one continuous
|
|
307
|
+
* video that follows the camera switches (V1 parity), instead of restarting
|
|
308
|
+
* per camera.
|
|
309
|
+
*
|
|
310
|
+
* The audio track from the initial stream is carried through unchanged across
|
|
311
|
+
* video switches, so there's no audio glitch when the camera flips.
|
|
312
|
+
*/
|
|
313
|
+
var CameraCompositor = class {
|
|
314
|
+
constructor(options = {}) {
|
|
315
|
+
this.canvas = null;
|
|
316
|
+
this.ctx = null;
|
|
317
|
+
this.video = null;
|
|
318
|
+
this.rafId = null;
|
|
319
|
+
this.audioTrack = null;
|
|
320
|
+
this.videoTrack = null;
|
|
321
|
+
this.composite = null;
|
|
322
|
+
this.fps = options.fps ?? 30;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Begin compositing `stream` and return the composite stream to record. The
|
|
326
|
+
* stream's audio track (if any) is carried through unchanged across later
|
|
327
|
+
* `switchVideo` calls.
|
|
328
|
+
*/
|
|
329
|
+
start(stream) {
|
|
330
|
+
this.canvas = document.createElement("canvas");
|
|
331
|
+
this.ctx = this.canvas.getContext("2d");
|
|
332
|
+
this.video = document.createElement("video");
|
|
333
|
+
this.video.muted = true;
|
|
334
|
+
this.video.playsInline = true;
|
|
335
|
+
this.audioTrack = stream.getAudioTracks()[0] ?? null;
|
|
336
|
+
this.setVideoSource(stream);
|
|
337
|
+
const composite = this.canvas.captureStream(this.fps);
|
|
338
|
+
if (this.audioTrack) composite.addTrack(this.audioTrack);
|
|
339
|
+
this.composite = composite;
|
|
340
|
+
this.startDrawLoop();
|
|
341
|
+
return composite;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Swap the camera feeding the canvas without interrupting the composite
|
|
345
|
+
* stream. Pass a video-only stream (audio is kept from `start`). The previous
|
|
346
|
+
* video track is stopped.
|
|
347
|
+
*/
|
|
348
|
+
switchVideo(stream) {
|
|
349
|
+
if (!this.composite) return;
|
|
350
|
+
const previousVideoTrack = this.videoTrack;
|
|
351
|
+
this.setVideoSource(stream);
|
|
352
|
+
if (previousVideoTrack) previousVideoTrack.stop();
|
|
353
|
+
}
|
|
354
|
+
/** The composite stream being recorded, if compositing is active. */
|
|
355
|
+
get stream() {
|
|
356
|
+
return this.composite ?? void 0;
|
|
357
|
+
}
|
|
358
|
+
/** Stop compositing and release the canvas, audio, and video tracks. */
|
|
359
|
+
stop() {
|
|
360
|
+
if (this.rafId !== null) {
|
|
361
|
+
cancelAnimationFrame(this.rafId);
|
|
362
|
+
this.rafId = null;
|
|
363
|
+
}
|
|
364
|
+
this.videoTrack?.stop();
|
|
365
|
+
this.audioTrack?.stop();
|
|
366
|
+
this.composite?.getTracks().forEach((track) => track.stop());
|
|
367
|
+
if (this.video) this.video.srcObject = null;
|
|
368
|
+
this.canvas = null;
|
|
369
|
+
this.ctx = null;
|
|
370
|
+
this.video = null;
|
|
371
|
+
this.videoTrack = null;
|
|
372
|
+
this.audioTrack = null;
|
|
373
|
+
this.composite = null;
|
|
374
|
+
}
|
|
375
|
+
setVideoSource(stream) {
|
|
376
|
+
this.videoTrack = stream.getVideoTracks()[0] ?? null;
|
|
377
|
+
const video = this.video;
|
|
378
|
+
if (!video) return;
|
|
379
|
+
video.srcObject = new MediaStream(stream.getVideoTracks());
|
|
380
|
+
video.play().catch(() => {});
|
|
381
|
+
}
|
|
382
|
+
startDrawLoop() {
|
|
383
|
+
const draw = () => {
|
|
384
|
+
const video = this.video;
|
|
385
|
+
const canvas = this.canvas;
|
|
386
|
+
const ctx = this.ctx;
|
|
387
|
+
if (video && canvas && ctx && video.videoWidth > 0) {
|
|
388
|
+
if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
|
|
389
|
+
canvas.width = video.videoWidth;
|
|
390
|
+
canvas.height = video.videoHeight;
|
|
391
|
+
}
|
|
392
|
+
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
393
|
+
}
|
|
394
|
+
this.rafId = requestAnimationFrame(draw);
|
|
395
|
+
};
|
|
396
|
+
this.rafId = requestAnimationFrame(draw);
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region ../infra/src/providers/browser/MultipartRecordingProvider.ts
|
|
402
|
+
/**
|
|
403
|
+
* S3 multipart requires every part except the last to be at least 5 MB, so we
|
|
404
|
+
* buffer recorded data until it crosses this threshold before emitting a part.
|
|
405
|
+
*/
|
|
406
|
+
const MIN_PART_SIZE_BYTES = 5 * 1024 * 1024;
|
|
407
|
+
/**
|
|
408
|
+
* How often `MediaRecorder` hands us buffered data (incremental blobs).
|
|
409
|
+
*/
|
|
410
|
+
const TIMESLICE_MS = 1e3;
|
|
411
|
+
/**
|
|
412
|
+
* Encoder target for the full video-selfie session. Higher than the
|
|
413
|
+
* short-clip liveness path (0.5–1 Mbps) so a multi-minute session keeps usable
|
|
414
|
+
* quality and accumulates past the 5 MB part threshold instead of compressing a
|
|
415
|
+
* near-static face down to a single sub-5 MB part. It's a ceiling — a still
|
|
416
|
+
* scene still encodes well below it.
|
|
417
|
+
*/
|
|
418
|
+
const VIDEO_BITS_PER_SECOND = 25e5;
|
|
419
|
+
/**
|
|
420
|
+
* Records a stream and streams it out as size-bounded parts for S3 multipart
|
|
421
|
+
* upload. Parts are emitted during recording (not buffered to the end), so the
|
|
422
|
+
* client never holds more than ~one part in memory regardless of video length.
|
|
423
|
+
*
|
|
424
|
+
* Unlike the short-clip liveness recorder, this does NOT auto-pause on
|
|
425
|
+
* `visibilitychange`: a video-selfie session must record continuously, and a
|
|
426
|
+
* pause with no resume would silently truncate the recording the moment the
|
|
427
|
+
* tab lost visibility.
|
|
428
|
+
*/
|
|
429
|
+
var MultipartRecordingProvider = class {
|
|
430
|
+
constructor() {
|
|
431
|
+
this.mediaRecorder = null;
|
|
432
|
+
this._isRecording = false;
|
|
433
|
+
this._hasError = false;
|
|
434
|
+
this._error = null;
|
|
435
|
+
this.mimeType = "";
|
|
436
|
+
this.buffer = [];
|
|
437
|
+
this.bufferedBytes = 0;
|
|
438
|
+
this.partNumber = 0;
|
|
439
|
+
this.onPart = null;
|
|
440
|
+
}
|
|
441
|
+
get isRecording() {
|
|
442
|
+
return this._isRecording;
|
|
443
|
+
}
|
|
444
|
+
get hasError() {
|
|
445
|
+
return this._hasError;
|
|
446
|
+
}
|
|
447
|
+
get error() {
|
|
448
|
+
return this._error;
|
|
449
|
+
}
|
|
450
|
+
startRecording(stream, onPart) {
|
|
451
|
+
this.reset();
|
|
452
|
+
this.buffer = [];
|
|
453
|
+
this.bufferedBytes = 0;
|
|
454
|
+
this.partNumber = 0;
|
|
455
|
+
this.onPart = onPart;
|
|
456
|
+
try {
|
|
457
|
+
this.mimeType = getSupportedMediaRecorderMimeType();
|
|
458
|
+
const recorder = new MediaRecorder(stream.clone(), {
|
|
459
|
+
mimeType: this.mimeType,
|
|
460
|
+
videoBitsPerSecond: VIDEO_BITS_PER_SECOND
|
|
461
|
+
});
|
|
462
|
+
recorder.ondataavailable = (event) => {
|
|
463
|
+
if (event.data.size > 0) {
|
|
464
|
+
this.buffer.push(event.data);
|
|
465
|
+
this.bufferedBytes += event.data.size;
|
|
466
|
+
if (this.bufferedBytes >= MIN_PART_SIZE_BYTES) this.flushPart();
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
recorder.onerror = (event) => {
|
|
470
|
+
this._error = `Recording error: ${event instanceof ErrorEvent && event.error instanceof Error ? event.error.message : event.type}`;
|
|
471
|
+
this._isRecording = false;
|
|
472
|
+
this._hasError = true;
|
|
473
|
+
};
|
|
474
|
+
recorder.start(TIMESLICE_MS);
|
|
475
|
+
this.mediaRecorder = recorder;
|
|
476
|
+
this._isRecording = true;
|
|
477
|
+
this._error = null;
|
|
478
|
+
this._hasError = false;
|
|
479
|
+
} catch (err) {
|
|
480
|
+
this._error = `Failed to start recording: ${err instanceof Error ? err.message : String(err)}`;
|
|
481
|
+
this._hasError = true;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
stopRecording() {
|
|
485
|
+
const recorder = this.mediaRecorder;
|
|
486
|
+
return new Promise((resolve) => {
|
|
487
|
+
if (recorder && this._isRecording) {
|
|
488
|
+
recorder.onstop = () => {
|
|
489
|
+
this.flushPart();
|
|
490
|
+
resolve();
|
|
491
|
+
};
|
|
492
|
+
recorder.stop();
|
|
493
|
+
this._isRecording = false;
|
|
494
|
+
} else resolve();
|
|
495
|
+
recorder?.stream?.getTracks().forEach((track) => track.stop());
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
reset() {
|
|
499
|
+
const recorder = this.mediaRecorder;
|
|
500
|
+
if (recorder) {
|
|
501
|
+
recorder.ondataavailable = null;
|
|
502
|
+
recorder.onerror = null;
|
|
503
|
+
recorder.onstop = null;
|
|
504
|
+
if (recorder.state !== "inactive") recorder.stop();
|
|
505
|
+
recorder.stream.getTracks().forEach((track) => track.stop());
|
|
506
|
+
this.mediaRecorder = null;
|
|
507
|
+
}
|
|
508
|
+
this.onPart = null;
|
|
509
|
+
this.buffer = [];
|
|
510
|
+
this.bufferedBytes = 0;
|
|
511
|
+
this._isRecording = false;
|
|
512
|
+
this._error = null;
|
|
513
|
+
this._hasError = false;
|
|
514
|
+
}
|
|
515
|
+
flushPart() {
|
|
516
|
+
if (this.bufferedBytes === 0 || !this.onPart) return;
|
|
517
|
+
const chunk = new Blob(this.buffer, { type: this.mimeType });
|
|
518
|
+
this.partNumber += 1;
|
|
519
|
+
this.onPart({
|
|
520
|
+
chunk,
|
|
521
|
+
partNumber: this.partNumber
|
|
522
|
+
});
|
|
523
|
+
this.buffer = [];
|
|
524
|
+
this.bufferedBytes = 0;
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
|
|
528
|
+
//#endregion
|
|
529
|
+
//#region src/internal/recordings/videoSelfieUploadService.ts
|
|
530
|
+
/**
|
|
531
|
+
* Opens a multipart video-selfie upload session. The returned `uploadId`
|
|
532
|
+
* identifies the session for subsequent part uploads and completion.
|
|
533
|
+
*/
|
|
534
|
+
async function initiateVideoSelfieUpload() {
|
|
535
|
+
return (await api.post(endpoints.videoSelfieUploadInitiate, {})).data.uploadId;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Uploads a single chunk as a multipart part. The body is the raw video bytes
|
|
539
|
+
* (no base64, no client-side encryption — encryption at rest is server-side
|
|
540
|
+
* S3 SSE). Returns the part number + `eTag` needed to complete the upload.
|
|
541
|
+
*/
|
|
542
|
+
async function uploadVideoSelfiePart(uploadId, partNumber, chunk) {
|
|
543
|
+
const response = await api.post(endpoints.videoSelfieUploadPart, chunk, {
|
|
544
|
+
query: {
|
|
545
|
+
uploadId,
|
|
546
|
+
partNumber
|
|
547
|
+
},
|
|
548
|
+
headers: { "Content-Type": "application/octet-stream" }
|
|
549
|
+
});
|
|
550
|
+
return {
|
|
551
|
+
partNumber: response.data.partNumber,
|
|
552
|
+
eTag: response.data.eTag
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Finalizes the upload. `parts` must be in ascending `partNumber` order, or
|
|
557
|
+
* S3 reassembly fails. Returns the server-reported status.
|
|
558
|
+
*/
|
|
559
|
+
async function completeVideoSelfieUpload(uploadId, parts) {
|
|
560
|
+
return (await api.post(endpoints.videoSelfieUploadComplete, {
|
|
561
|
+
uploadId,
|
|
562
|
+
parts
|
|
563
|
+
})).data.status;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
//#endregion
|
|
567
|
+
//#region src/internal/recordings/multipartRecordingService.ts
|
|
568
|
+
/**
|
|
569
|
+
* Records the camera on-device and streams it to the backend as it is
|
|
570
|
+
* captured, using the S3 multipart upload protocol
|
|
571
|
+
* (`/omni/video-selfie/upload/*`).
|
|
572
|
+
*
|
|
573
|
+
* This is the Video Selfie module's client-side recorder and is deliberately
|
|
574
|
+
* independent of the face-capture recording services (`LocalRecordingService`
|
|
575
|
+
* / `OpenViduRecordingService`) — it is the replacement for server-side
|
|
576
|
+
* (OpenVidu) recording, not a sibling of it.
|
|
577
|
+
*
|
|
578
|
+
* Parts upload concurrently with recording, so `stop()` only flushes the final
|
|
579
|
+
* part and finalizes — keeping the whole session inside the server's
|
|
580
|
+
* video-selfie timeout regardless of length, with client memory bounded to
|
|
581
|
+
* ~one part. No client-side encryption: video is encrypted at rest server-side
|
|
582
|
+
* (S3 SSE-AES-256).
|
|
583
|
+
*/
|
|
584
|
+
var MultipartRecordingService = class {
|
|
585
|
+
constructor() {
|
|
586
|
+
this.recorder = new MultipartRecordingProvider();
|
|
587
|
+
this.uploadId = null;
|
|
588
|
+
this.parts = [];
|
|
589
|
+
this.inflight = [];
|
|
590
|
+
this.uploadFailed = false;
|
|
591
|
+
}
|
|
592
|
+
get isRecording() {
|
|
593
|
+
return this.recorder.isRecording;
|
|
594
|
+
}
|
|
595
|
+
async start(stream) {
|
|
596
|
+
this.uploadId = await initiateVideoSelfieUpload();
|
|
597
|
+
this.parts = [];
|
|
598
|
+
this.inflight = [];
|
|
599
|
+
this.uploadFailed = false;
|
|
600
|
+
this.recorder.startRecording(stream, ({ chunk, partNumber }) => {
|
|
601
|
+
const uploadId = this.uploadId;
|
|
602
|
+
if (!uploadId) return;
|
|
603
|
+
const task = uploadVideoSelfiePart(uploadId, partNumber, chunk).then((part) => {
|
|
604
|
+
this.parts.push(part);
|
|
605
|
+
}).catch(() => {
|
|
606
|
+
this.uploadFailed = true;
|
|
607
|
+
});
|
|
608
|
+
this.inflight.push(task);
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
async stop() {
|
|
612
|
+
const uploadId = this.uploadId;
|
|
613
|
+
if (!this.recorder.isRecording || !uploadId) return { recordingId: null };
|
|
614
|
+
await this.recorder.stopRecording();
|
|
615
|
+
await Promise.all(this.inflight);
|
|
616
|
+
if (this.uploadFailed || this.parts.length === 0) return { recordingId: null };
|
|
617
|
+
const orderedParts = [...this.parts].sort((a, b) => a.partNumber - b.partNumber);
|
|
618
|
+
try {
|
|
619
|
+
await completeVideoSelfieUpload(uploadId, orderedParts);
|
|
620
|
+
return { recordingId: uploadId };
|
|
621
|
+
} catch {
|
|
622
|
+
return { recordingId: null };
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
cleanup() {
|
|
626
|
+
this.recorder.reset();
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/modules/video-selfie/videoSelfieRecordingMachine.ts
|
|
632
|
+
/** A detector that never settles — used when a step has no auto-capture yet. */
|
|
633
|
+
const manualStepDetector = () => new Promise(() => {});
|
|
634
|
+
function defaultAcquireCamera(facingMode, withAudio) {
|
|
635
|
+
return requestCameraWithFallback({
|
|
636
|
+
video: { facingMode },
|
|
637
|
+
audio: withAudio
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
function eventOutputStream(event) {
|
|
641
|
+
return event.output;
|
|
642
|
+
}
|
|
643
|
+
function recordingIdFromEvent(event) {
|
|
644
|
+
return event.output.recordingId;
|
|
645
|
+
}
|
|
646
|
+
function errorFromEvent(event) {
|
|
647
|
+
return String(event.error);
|
|
648
|
+
}
|
|
649
|
+
function detectionStatusFromEvent(event) {
|
|
650
|
+
return event.status;
|
|
651
|
+
}
|
|
652
|
+
function detectionErrorFromEvent(event) {
|
|
653
|
+
return event.error;
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Orchestrates a multi-step Video Selfie session as ONE continuous recording
|
|
657
|
+
* that follows the camera between steps AND auto-advances per step:
|
|
658
|
+
*
|
|
659
|
+
* idle ─START▶ acquiringCamera ─▶ starting ─▶ recording ⇄ switchingCamera
|
|
660
|
+
* │ (capture validated → DETECTION_DONE,
|
|
661
|
+
* │ or manual NEXT_STEP)
|
|
662
|
+
* last step ─▶ finalizing ─▶ finished
|
|
663
|
+
*
|
|
664
|
+
* While `recording`, a per-step detector runs WASM auto-capture + backend
|
|
665
|
+
* validation; success advances the step (switching the camera when the next
|
|
666
|
+
* step's facing differs), and the final step finalizes the multipart upload.
|
|
667
|
+
*
|
|
668
|
+
* Teardown: `ABORT` is accepted from ANY non-final state and every terminal
|
|
669
|
+
* state runs `cleanup` on entry, so no settled path leaves camera or mic
|
|
670
|
+
* tracks running. XState v5 executes NO exit actions on an external
|
|
671
|
+
* `actor.stop()`, so the manager sends `ABORT` before stopping the actor —
|
|
672
|
+
* never stop this actor directly without aborting first.
|
|
673
|
+
*/
|
|
674
|
+
const videoSelfieRecordingSetup = setup({
|
|
675
|
+
types: {
|
|
676
|
+
context: {},
|
|
677
|
+
events: {},
|
|
678
|
+
input: {}
|
|
679
|
+
},
|
|
680
|
+
actors: {
|
|
681
|
+
acquireCamera: fromPromise(async ({ input, signal }) => {
|
|
682
|
+
const stream = await input.acquire(input.facingMode, input.withAudio);
|
|
683
|
+
if (signal.aborted) {
|
|
684
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
685
|
+
throw new Error("Camera acquire cancelled");
|
|
686
|
+
}
|
|
687
|
+
return stream;
|
|
688
|
+
}),
|
|
689
|
+
startRecording: fromPromise(async ({ input }) => {
|
|
690
|
+
await input.service.start(input.stream);
|
|
691
|
+
}),
|
|
692
|
+
finalizeRecording: fromPromise(async ({ input }) => input.service.stop()),
|
|
693
|
+
detectStep: fromCallback(({ input, sendBack }) => {
|
|
694
|
+
const controller = new AbortController();
|
|
695
|
+
input.detect({
|
|
696
|
+
step: input.step,
|
|
697
|
+
stream: input.stream,
|
|
698
|
+
signal: controller.signal,
|
|
699
|
+
onStatus: (status) => sendBack({
|
|
700
|
+
type: "DETECTION_STATUS",
|
|
701
|
+
status
|
|
702
|
+
})
|
|
703
|
+
}).then(() => sendBack({ type: "DETECTION_DONE" })).catch((err) => {
|
|
704
|
+
if (!controller.signal.aborted) sendBack({
|
|
705
|
+
type: "DETECTION_FAILED",
|
|
706
|
+
error: String(err)
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
return () => controller.abort();
|
|
710
|
+
})
|
|
711
|
+
},
|
|
712
|
+
actions: {
|
|
713
|
+
advanceStep: assign({ stepIndex: ({ context: context$1 }) => context$1.stepIndex + 1 }),
|
|
714
|
+
setDetectionStatus: assign({ detectionStatus: ({ event }) => detectionStatusFromEvent(event) }),
|
|
715
|
+
setDetectionError: assign({ detectionError: ({ event }) => detectionErrorFromEvent(event) }),
|
|
716
|
+
setErrorFromDetection: assign({ error: ({ event }) => detectionErrorFromEvent(event) }),
|
|
717
|
+
clearDetectionFeedback: assign({
|
|
718
|
+
detectionStatus: () => void 0,
|
|
719
|
+
detectionError: () => void 0
|
|
720
|
+
}),
|
|
721
|
+
setUploadFailedError: assign({ error: () => "Video upload failed" }),
|
|
722
|
+
cleanup: ({ context: context$1 }) => {
|
|
723
|
+
context$1.service.cleanup();
|
|
724
|
+
context$1.compositor.stop();
|
|
725
|
+
context$1.stream?.getTracks().forEach((track) => track.stop());
|
|
726
|
+
}
|
|
727
|
+
},
|
|
728
|
+
guards: {
|
|
729
|
+
isLastStep: ({ context: context$1 }) => context$1.stepIndex >= context$1.steps.length - 1,
|
|
730
|
+
nextStepFlipsCamera: ({ context: context$1 }) => facingModeForStep(context$1.steps[context$1.stepIndex]) !== facingModeForStep(context$1.steps[context$1.stepIndex + 1]),
|
|
731
|
+
uploadReturnedNoRecordingId: ({ event }) => recordingIdFromEvent(event) === null
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
const ADVANCE_TRANSITIONS = [
|
|
735
|
+
{
|
|
736
|
+
target: "finalizing",
|
|
737
|
+
guard: "isLastStep"
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
target: "switchingCamera",
|
|
741
|
+
guard: "nextStepFlipsCamera",
|
|
742
|
+
actions: "advanceStep"
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
target: "recording",
|
|
746
|
+
reenter: true,
|
|
747
|
+
actions: "advanceStep"
|
|
748
|
+
}
|
|
749
|
+
];
|
|
750
|
+
const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
751
|
+
id: "videoSelfieRecording",
|
|
752
|
+
initial: "idle",
|
|
753
|
+
context: ({ input }) => ({
|
|
754
|
+
service: input.dependencies?.createRecordingService?.() ?? new MultipartRecordingService(),
|
|
755
|
+
compositor: input.dependencies?.createCompositor?.() ?? new CameraCompositor(),
|
|
756
|
+
acquireCamera: input.dependencies?.acquireCamera ?? defaultAcquireCamera,
|
|
757
|
+
detect: input.dependencies?.detect ?? manualStepDetector,
|
|
758
|
+
steps: input.steps && input.steps.length > 0 ? input.steps : ["selfie"],
|
|
759
|
+
stepIndex: 0,
|
|
760
|
+
stream: void 0,
|
|
761
|
+
composite: void 0,
|
|
762
|
+
detectionStatus: void 0,
|
|
763
|
+
detectionError: void 0,
|
|
764
|
+
recordingId: null,
|
|
765
|
+
error: void 0
|
|
766
|
+
}),
|
|
767
|
+
on: { ABORT: { target: ".aborted" } },
|
|
768
|
+
states: {
|
|
769
|
+
idle: { on: { START: { target: "acquiringCamera" } } },
|
|
770
|
+
acquiringCamera: { invoke: {
|
|
771
|
+
src: "acquireCamera",
|
|
772
|
+
input: ({ context: context$1 }) => ({
|
|
773
|
+
acquire: context$1.acquireCamera,
|
|
774
|
+
facingMode: facingModeForStep(context$1.steps[context$1.stepIndex]),
|
|
775
|
+
withAudio: true
|
|
776
|
+
}),
|
|
777
|
+
onDone: {
|
|
778
|
+
target: "starting",
|
|
779
|
+
actions: [({ context: context$1, event }) => {
|
|
780
|
+
context$1.compositor.start(eventOutputStream(event));
|
|
781
|
+
}, assign({
|
|
782
|
+
stream: ({ event }) => eventOutputStream(event),
|
|
783
|
+
composite: ({ context: context$1 }) => context$1.compositor.stream
|
|
784
|
+
})]
|
|
785
|
+
},
|
|
786
|
+
onError: {
|
|
787
|
+
target: "error",
|
|
788
|
+
actions: assign({ error: ({ event }) => errorFromEvent(event) })
|
|
789
|
+
}
|
|
790
|
+
} },
|
|
791
|
+
starting: { invoke: {
|
|
792
|
+
src: "startRecording",
|
|
793
|
+
input: ({ context: context$1 }) => ({
|
|
794
|
+
service: context$1.service,
|
|
795
|
+
stream: context$1.composite
|
|
796
|
+
}),
|
|
797
|
+
onDone: { target: "recording" },
|
|
798
|
+
onError: {
|
|
799
|
+
target: "error",
|
|
800
|
+
actions: assign({ error: ({ event }) => errorFromEvent(event) })
|
|
801
|
+
}
|
|
802
|
+
} },
|
|
803
|
+
recording: {
|
|
804
|
+
entry: "clearDetectionFeedback",
|
|
805
|
+
invoke: {
|
|
806
|
+
src: "detectStep",
|
|
807
|
+
input: ({ context: context$1 }) => ({
|
|
808
|
+
detect: context$1.detect,
|
|
809
|
+
step: context$1.steps[context$1.stepIndex],
|
|
810
|
+
stream: context$1.stream
|
|
811
|
+
})
|
|
812
|
+
},
|
|
813
|
+
on: {
|
|
814
|
+
DETECTION_STATUS: { actions: "setDetectionStatus" },
|
|
815
|
+
DETECTION_DONE: ADVANCE_TRANSITIONS,
|
|
816
|
+
NEXT_STEP: ADVANCE_TRANSITIONS,
|
|
817
|
+
DETECTION_FAILED: {
|
|
818
|
+
target: "error",
|
|
819
|
+
actions: "setErrorFromDetection"
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
},
|
|
823
|
+
switchingCamera: { invoke: {
|
|
824
|
+
src: "acquireCamera",
|
|
825
|
+
input: ({ context: context$1 }) => ({
|
|
826
|
+
acquire: context$1.acquireCamera,
|
|
827
|
+
facingMode: facingModeForStep(context$1.steps[context$1.stepIndex]),
|
|
828
|
+
withAudio: false
|
|
829
|
+
}),
|
|
830
|
+
onDone: {
|
|
831
|
+
target: "recording",
|
|
832
|
+
actions: [({ context: context$1, event }) => {
|
|
833
|
+
context$1.compositor.switchVideo(eventOutputStream(event));
|
|
834
|
+
}, assign({ stream: ({ event }) => eventOutputStream(event) })]
|
|
835
|
+
},
|
|
836
|
+
onError: {
|
|
837
|
+
target: "error",
|
|
838
|
+
actions: assign({ error: ({ event }) => errorFromEvent(event) })
|
|
839
|
+
}
|
|
840
|
+
} },
|
|
841
|
+
finalizing: { invoke: {
|
|
842
|
+
src: "finalizeRecording",
|
|
843
|
+
input: ({ context: context$1 }) => ({ service: context$1.service }),
|
|
844
|
+
onDone: [{
|
|
845
|
+
target: "error",
|
|
846
|
+
guard: "uploadReturnedNoRecordingId",
|
|
847
|
+
actions: [assign({ recordingId: ({ event }) => recordingIdFromEvent(event) }), "setUploadFailedError"]
|
|
848
|
+
}, {
|
|
849
|
+
target: "finished",
|
|
850
|
+
actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
|
|
851
|
+
}],
|
|
852
|
+
onError: {
|
|
853
|
+
target: "error",
|
|
854
|
+
actions: assign({ error: ({ event }) => errorFromEvent(event) })
|
|
855
|
+
}
|
|
856
|
+
} },
|
|
857
|
+
finished: {
|
|
858
|
+
type: "final",
|
|
859
|
+
entry: "cleanup"
|
|
860
|
+
},
|
|
861
|
+
error: {
|
|
862
|
+
type: "final",
|
|
863
|
+
entry: "cleanup"
|
|
864
|
+
},
|
|
865
|
+
aborted: {
|
|
866
|
+
type: "final",
|
|
867
|
+
entry: "cleanup"
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
});
|
|
871
|
+
const videoSelfieRecordingMachine = _videoSelfieRecordingMachine;
|
|
872
|
+
|
|
873
|
+
//#endregion
|
|
874
|
+
//#region src/modules/video-selfie/videoSelfieRecordingActor.ts
|
|
875
|
+
function createVideoSelfieRecordingActor(options = {}) {
|
|
876
|
+
const config = options.config;
|
|
877
|
+
const detect = options.dependencies?.detect ?? (config ? createDefaultStepDetector(config, { getWasmUtil: () => WasmUtilProvider.getInstance() }) : void 0);
|
|
878
|
+
return createActor(videoSelfieRecordingMachine, { input: {
|
|
879
|
+
steps: config ? resolveVideoSelfieSteps(config) : void 0,
|
|
880
|
+
dependencies: {
|
|
881
|
+
...options.dependencies,
|
|
882
|
+
detect
|
|
883
|
+
}
|
|
884
|
+
} }).start();
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
//#endregion
|
|
888
|
+
//#region src/modules/video-selfie/videoSelfieRecordingManager.ts
|
|
889
|
+
function mapState(snapshot) {
|
|
890
|
+
const value = typeof snapshot.value === "string" ? snapshot.value : "idle";
|
|
891
|
+
const { context: context$1 } = snapshot;
|
|
892
|
+
const status = value === "acquiringCamera" ? "starting" : value;
|
|
893
|
+
const steps = context$1.steps;
|
|
894
|
+
const stepIndex = context$1.stepIndex;
|
|
895
|
+
return {
|
|
896
|
+
status,
|
|
897
|
+
step: steps[stepIndex],
|
|
898
|
+
stepIndex,
|
|
899
|
+
isLastStep: stepIndex >= steps.length - 1,
|
|
900
|
+
stream: context$1.stream,
|
|
901
|
+
composite: context$1.composite,
|
|
902
|
+
detectionStatus: context$1.detectionStatus,
|
|
903
|
+
recordingId: context$1.recordingId,
|
|
904
|
+
error: context$1.error
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
function createApi({ actor }) {
|
|
908
|
+
return {
|
|
909
|
+
start() {
|
|
910
|
+
actor.send({ type: "START" });
|
|
911
|
+
},
|
|
912
|
+
nextStep() {
|
|
913
|
+
actor.send({ type: "NEXT_STEP" });
|
|
914
|
+
},
|
|
915
|
+
abort() {
|
|
916
|
+
actor.send({ type: "ABORT" });
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Creates a manager for a multi-step, camera-switching Video Selfie recording.
|
|
922
|
+
*
|
|
923
|
+
* The whole session records as one continuous video that follows the camera
|
|
924
|
+
* across steps (front for selfie, rear for ID). Exposes `getState()` /
|
|
925
|
+
* `subscribe()` (status, step, preview `stream`, `recordingId`) plus `start()`,
|
|
926
|
+
* `nextStep()`, and `abort()`.
|
|
927
|
+
*/
|
|
928
|
+
function createVideoSelfieRecordingManager(options = {}) {
|
|
929
|
+
const actor = createVideoSelfieRecordingActor(options);
|
|
930
|
+
const manager = createManager({
|
|
931
|
+
actor,
|
|
932
|
+
mapState,
|
|
933
|
+
createApi
|
|
934
|
+
});
|
|
935
|
+
return {
|
|
936
|
+
...manager,
|
|
937
|
+
stop() {
|
|
938
|
+
if (actor.getSnapshot().status === "active") actor.send({ type: "ABORT" });
|
|
939
|
+
manager.stop();
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
//#endregion
|
|
945
|
+
//#region src/modules/video-selfie/videoSelfieWasm.ts
|
|
946
|
+
/**
|
|
947
|
+
* Warms the WASM pipelines the Video Selfie flow needs (face detection, plus the
|
|
948
|
+
* ID-capture pipeline when `checkIdScan` is on), reusing the WASM config stored
|
|
949
|
+
* by `setup({ wasm })`.
|
|
950
|
+
*
|
|
951
|
+
* Idempotent and safe to call eagerly — the UI invokes it when the tutorial
|
|
952
|
+
* mounts so the models finish loading while the user reads the instructions, and
|
|
953
|
+
* detection is ready the instant recording starts (mirrors how the selfie and ID
|
|
954
|
+
* modules warm up during their tutorials). No-op when WASM was not configured.
|
|
955
|
+
*/
|
|
956
|
+
function warmupVideoSelfieWasm(config) {
|
|
957
|
+
return ensureWasmPipelines(videoSelfieWasmPipelines(config));
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
//#endregion
|
|
961
|
+
//#region src/modules/video-selfie/videoSelfieCameraWarmer.ts
|
|
962
|
+
function defaultAcquire(facingMode, withAudio) {
|
|
963
|
+
return requestCameraWithFallback({
|
|
964
|
+
video: { facingMode },
|
|
965
|
+
audio: withAudio
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
function stopStream(stream) {
|
|
969
|
+
stream?.getTracks().forEach((track) => track.stop());
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Creates a {@link VideoSelfieCameraWarmer} for the given config. The first
|
|
973
|
+
* camera (and whether it needs audio) is derived from the resolved step order —
|
|
974
|
+
* the same first acquire the recording machine performs on `START`.
|
|
975
|
+
*/
|
|
976
|
+
function createVideoSelfieCameraWarmer(config) {
|
|
977
|
+
const firstFacing = facingModeForStep(resolveVideoSelfieSteps(config)[0] ?? "selfie");
|
|
978
|
+
let warming = null;
|
|
979
|
+
let warmed = null;
|
|
980
|
+
let consumed = false;
|
|
981
|
+
return {
|
|
982
|
+
warm() {
|
|
983
|
+
if (warming) return;
|
|
984
|
+
warming = (async () => {
|
|
985
|
+
try {
|
|
986
|
+
if (await checkPermission() !== "granted") return null;
|
|
987
|
+
warmed = await defaultAcquire(firstFacing, true);
|
|
988
|
+
return warmed;
|
|
989
|
+
} catch {
|
|
990
|
+
return null;
|
|
991
|
+
}
|
|
992
|
+
})();
|
|
993
|
+
},
|
|
994
|
+
async acquireCamera(facingMode, withAudio) {
|
|
995
|
+
if (!consumed && facingMode === firstFacing && withAudio) {
|
|
996
|
+
consumed = true;
|
|
997
|
+
const stream = warmed ?? (warming ? await warming : null);
|
|
998
|
+
warmed = null;
|
|
999
|
+
if (stream) return stream;
|
|
1000
|
+
}
|
|
1001
|
+
return defaultAcquire(facingMode, withAudio);
|
|
1002
|
+
},
|
|
1003
|
+
dispose() {
|
|
1004
|
+
if (consumed) return;
|
|
1005
|
+
if (warmed) {
|
|
1006
|
+
stopStream(warmed);
|
|
1007
|
+
warmed = null;
|
|
1008
|
+
} else if (warming) warming.then((stream) => {
|
|
1009
|
+
if (!consumed) stopStream(stream);
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
//#endregion
|
|
1016
|
+
//#region src/modules/video-selfie/videoSelfieSpeechService.ts
|
|
1017
|
+
/**
|
|
1018
|
+
* Fetches `count` random challenge questions the user answers out loud. The
|
|
1019
|
+
* spoken answers are captured in the main video recording (no separate upload)
|
|
1020
|
+
* and verified server-side.
|
|
1021
|
+
*/
|
|
1022
|
+
async function getVideoSelfieQuestions(count) {
|
|
1023
|
+
return (await api.get(endpoints.questionAndAnswer, { query: {
|
|
1024
|
+
numberOfQuestions: count,
|
|
1025
|
+
returnVoiceConsentQuestion: false
|
|
1026
|
+
} })).data.questionAndAnswers ?? [];
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Fetches the voice-consent phrase the user must read aloud. The phrase is the
|
|
1030
|
+
* `answer` of the consent question returned when `returnVoiceConsentQuestion`
|
|
1031
|
+
* is requested with no challenge questions.
|
|
1032
|
+
*/
|
|
1033
|
+
async function getVoiceConsentPhrase() {
|
|
1034
|
+
return (await api.get(endpoints.questionAndAnswer, { query: {
|
|
1035
|
+
numberOfQuestions: 0,
|
|
1036
|
+
returnVoiceConsentQuestion: true
|
|
1037
|
+
} })).data.questionAndAnswers?.[0]?.answer ?? "";
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Uploads the recorded consent audio (base64 MP3) for speech-to-text
|
|
1041
|
+
* validation. Resolves `{ success: false }` on a network/validation error so
|
|
1042
|
+
* the UI can drive its retry loop instead of throwing.
|
|
1043
|
+
*/
|
|
1044
|
+
async function addSpeech(base64Mp3) {
|
|
1045
|
+
try {
|
|
1046
|
+
return { success: (await api.post(endpoints.addSpeechV2, {
|
|
1047
|
+
base64Media: base64Mp3,
|
|
1048
|
+
audioOnly: true
|
|
1049
|
+
})).data.success === true };
|
|
1050
|
+
} catch {
|
|
1051
|
+
return { success: false };
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
//#endregion
|
|
1056
|
+
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/wave-encoder/index.js
|
|
1057
|
+
var wave_encoder_default = () => {
|
|
1058
|
+
let BYTES_PER_SAMPLE = 2;
|
|
1059
|
+
let recorded = [];
|
|
1060
|
+
function encode(buffer) {
|
|
1061
|
+
let length = buffer.length;
|
|
1062
|
+
let data = new Uint8Array(length * BYTES_PER_SAMPLE);
|
|
1063
|
+
for (let i = 0; i < length; i++) {
|
|
1064
|
+
let index = i * BYTES_PER_SAMPLE;
|
|
1065
|
+
let sample = buffer[i];
|
|
1066
|
+
if (sample > 1) sample = 1;
|
|
1067
|
+
else if (sample < -1) sample = -1;
|
|
1068
|
+
sample = sample * 32768;
|
|
1069
|
+
data[index] = sample;
|
|
1070
|
+
data[index + 1] = sample >> 8;
|
|
1071
|
+
}
|
|
1072
|
+
recorded.push(data);
|
|
1073
|
+
}
|
|
1074
|
+
function dump(sampleRate) {
|
|
1075
|
+
let bufferLength = recorded.length ? recorded[0].length : 0;
|
|
1076
|
+
let length = recorded.length * bufferLength;
|
|
1077
|
+
let wav = new Uint8Array(44 + length);
|
|
1078
|
+
let view = new DataView(wav.buffer);
|
|
1079
|
+
view.setUint32(0, 1380533830, false);
|
|
1080
|
+
view.setUint32(4, 36 + length, true);
|
|
1081
|
+
view.setUint32(8, 1463899717, false);
|
|
1082
|
+
view.setUint32(12, 1718449184, false);
|
|
1083
|
+
view.setUint32(16, 16, true);
|
|
1084
|
+
view.setUint16(20, 1, true);
|
|
1085
|
+
view.setUint16(22, 1, true);
|
|
1086
|
+
view.setUint32(24, sampleRate, true);
|
|
1087
|
+
view.setUint32(28, sampleRate * BYTES_PER_SAMPLE, true);
|
|
1088
|
+
view.setUint16(32, BYTES_PER_SAMPLE, true);
|
|
1089
|
+
view.setUint16(34, 8 * BYTES_PER_SAMPLE, true);
|
|
1090
|
+
view.setUint32(36, 1684108385, false);
|
|
1091
|
+
view.setUint32(40, length, true);
|
|
1092
|
+
for (let i = 0; i < recorded.length; i++) wav.set(recorded[i], i * bufferLength + 44);
|
|
1093
|
+
recorded = [];
|
|
1094
|
+
postMessage(wav.buffer, [wav.buffer]);
|
|
1095
|
+
}
|
|
1096
|
+
onmessage = (e) => {
|
|
1097
|
+
if (e.data[0] === "encode") encode(e.data[1]);
|
|
1098
|
+
else if (e.data[0] === "dump") dump(e.data[1]);
|
|
1099
|
+
};
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
//#endregion
|
|
1103
|
+
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/index.js
|
|
1104
|
+
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
1105
|
+
let createWorker = (fn) => {
|
|
1106
|
+
let js = fn.toString().replace(/^(\(\)\s*=>|function\s*\(\))\s*{/, "").replace(/}$/, "");
|
|
1107
|
+
let blob = new Blob([js]);
|
|
1108
|
+
return new Worker(URL.createObjectURL(blob));
|
|
1109
|
+
};
|
|
1110
|
+
let error = (method) => {
|
|
1111
|
+
let event = new Event("error");
|
|
1112
|
+
event.data = /* @__PURE__ */ new Error("Wrong state for " + method);
|
|
1113
|
+
return event;
|
|
1114
|
+
};
|
|
1115
|
+
let context;
|
|
1116
|
+
/**
|
|
1117
|
+
* Audio Recorder with MediaRecorder API.
|
|
1118
|
+
*
|
|
1119
|
+
* @example
|
|
1120
|
+
* navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
|
|
1121
|
+
* let recorder = new MediaRecorder(stream)
|
|
1122
|
+
* })
|
|
1123
|
+
*/
|
|
1124
|
+
var MediaRecorder$1 = class MediaRecorder$1 {
|
|
1125
|
+
/**
|
|
1126
|
+
* @param {MediaStream} stream The audio stream to record.
|
|
1127
|
+
*/
|
|
1128
|
+
constructor(stream, config = null) {
|
|
1129
|
+
/**
|
|
1130
|
+
* The `MediaStream` passed into the constructor.
|
|
1131
|
+
* @type {MediaStream}
|
|
1132
|
+
*/
|
|
1133
|
+
this.stream = stream;
|
|
1134
|
+
this.config = config;
|
|
1135
|
+
/**
|
|
1136
|
+
* The current state of recording process.
|
|
1137
|
+
* @type {"inactive"|"recording"|"paused"}
|
|
1138
|
+
*/
|
|
1139
|
+
this.state = "inactive";
|
|
1140
|
+
this.em = document.createDocumentFragment();
|
|
1141
|
+
this.encoder = createWorker(MediaRecorder$1.encoder);
|
|
1142
|
+
let recorder = this;
|
|
1143
|
+
this.encoder.addEventListener("message", (e) => {
|
|
1144
|
+
let event = new Event("dataavailable");
|
|
1145
|
+
event.data = new Blob([e.data], { type: recorder.mimeType });
|
|
1146
|
+
recorder.em.dispatchEvent(event);
|
|
1147
|
+
if (recorder.state === "inactive") recorder.em.dispatchEvent(new Event("stop"));
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Begins recording media.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {number} [timeslice] The milliseconds to record into each `Blob`.
|
|
1154
|
+
* If this parameter isn’t included, single `Blob`
|
|
1155
|
+
* will be recorded.
|
|
1156
|
+
*
|
|
1157
|
+
* @return {undefined}
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* recordButton.addEventListener('click', () => {
|
|
1161
|
+
* recorder.start()
|
|
1162
|
+
* })
|
|
1163
|
+
*/
|
|
1164
|
+
start(timeslice) {
|
|
1165
|
+
if (this.state !== "inactive") return this.em.dispatchEvent(error("start"));
|
|
1166
|
+
this.state = "recording";
|
|
1167
|
+
if (!context) context = new AudioContext(this.config);
|
|
1168
|
+
this.clone = this.stream.clone();
|
|
1169
|
+
this.input = context.createMediaStreamSource(this.clone);
|
|
1170
|
+
this.processor = context.createScriptProcessor(2048, 1, 1);
|
|
1171
|
+
this.encoder.postMessage(["init", context.sampleRate]);
|
|
1172
|
+
this.processor.onaudioprocess = (e) => {
|
|
1173
|
+
if (this.state === "recording") this.encoder.postMessage(["encode", e.inputBuffer.getChannelData(0)]);
|
|
1174
|
+
};
|
|
1175
|
+
this.input.connect(this.processor);
|
|
1176
|
+
this.processor.connect(context.destination);
|
|
1177
|
+
this.em.dispatchEvent(new Event("start"));
|
|
1178
|
+
if (timeslice) this.slicing = setInterval(() => {
|
|
1179
|
+
if (this.state === "recording") this.requestData();
|
|
1180
|
+
}, timeslice);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Stop media capture and raise `dataavailable` event with recorded data.
|
|
1184
|
+
*
|
|
1185
|
+
* @return {undefined}
|
|
1186
|
+
*
|
|
1187
|
+
* @example
|
|
1188
|
+
* finishButton.addEventListener('click', () => {
|
|
1189
|
+
* recorder.stop()
|
|
1190
|
+
* })
|
|
1191
|
+
*/
|
|
1192
|
+
stop() {
|
|
1193
|
+
if (this.state === "inactive") return this.em.dispatchEvent(error("stop"));
|
|
1194
|
+
this.requestData();
|
|
1195
|
+
this.state = "inactive";
|
|
1196
|
+
this.clone.getTracks().forEach((track) => {
|
|
1197
|
+
track.stop();
|
|
1198
|
+
});
|
|
1199
|
+
this.processor.disconnect();
|
|
1200
|
+
this.input.disconnect();
|
|
1201
|
+
return clearInterval(this.slicing);
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* Pauses recording of media streams.
|
|
1205
|
+
*
|
|
1206
|
+
* @return {undefined}
|
|
1207
|
+
*
|
|
1208
|
+
* @example
|
|
1209
|
+
* pauseButton.addEventListener('click', () => {
|
|
1210
|
+
* recorder.pause()
|
|
1211
|
+
* })
|
|
1212
|
+
*/
|
|
1213
|
+
pause() {
|
|
1214
|
+
if (this.state !== "recording") return this.em.dispatchEvent(error("pause"));
|
|
1215
|
+
this.state = "paused";
|
|
1216
|
+
return this.em.dispatchEvent(new Event("pause"));
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Resumes media recording when it has been previously paused.
|
|
1220
|
+
*
|
|
1221
|
+
* @return {undefined}
|
|
1222
|
+
*
|
|
1223
|
+
* @example
|
|
1224
|
+
* resumeButton.addEventListener('click', () => {
|
|
1225
|
+
* recorder.resume()
|
|
1226
|
+
* })
|
|
1227
|
+
*/
|
|
1228
|
+
resume() {
|
|
1229
|
+
if (this.state !== "paused") return this.em.dispatchEvent(error("resume"));
|
|
1230
|
+
this.state = "recording";
|
|
1231
|
+
return this.em.dispatchEvent(new Event("resume"));
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* Raise a `dataavailable` event containing the captured media.
|
|
1235
|
+
*
|
|
1236
|
+
* @return {undefined}
|
|
1237
|
+
*
|
|
1238
|
+
* @example
|
|
1239
|
+
* this.on('nextData', () => {
|
|
1240
|
+
* recorder.requestData()
|
|
1241
|
+
* })
|
|
1242
|
+
*/
|
|
1243
|
+
requestData() {
|
|
1244
|
+
if (this.state === "inactive") return this.em.dispatchEvent(error("requestData"));
|
|
1245
|
+
return this.encoder.postMessage(["dump", context.sampleRate]);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Add listener for specified event type.
|
|
1249
|
+
*
|
|
1250
|
+
* @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
|
|
1251
|
+
* type Event type.
|
|
1252
|
+
* @param {function} listener The listener function.
|
|
1253
|
+
*
|
|
1254
|
+
* @return {undefined}
|
|
1255
|
+
*
|
|
1256
|
+
* @example
|
|
1257
|
+
* recorder.addEventListener('dataavailable', e => {
|
|
1258
|
+
* audio.src = URL.createObjectURL(e.data)
|
|
1259
|
+
* })
|
|
1260
|
+
*/
|
|
1261
|
+
addEventListener(...args) {
|
|
1262
|
+
this.em.addEventListener(...args);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* Remove event listener.
|
|
1266
|
+
*
|
|
1267
|
+
* @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
|
|
1268
|
+
* type Event type.
|
|
1269
|
+
* @param {function} listener The same function used in `addEventListener`.
|
|
1270
|
+
*
|
|
1271
|
+
* @return {undefined}
|
|
1272
|
+
*/
|
|
1273
|
+
removeEventListener(...args) {
|
|
1274
|
+
this.em.removeEventListener(...args);
|
|
1275
|
+
}
|
|
1276
|
+
/**
|
|
1277
|
+
* Calls each of the listeners registered for a given event.
|
|
1278
|
+
*
|
|
1279
|
+
* @param {Event} event The event object.
|
|
1280
|
+
*
|
|
1281
|
+
* @return {boolean} Is event was no canceled by any listener.
|
|
1282
|
+
*/
|
|
1283
|
+
dispatchEvent(...args) {
|
|
1284
|
+
this.em.dispatchEvent(...args);
|
|
1285
|
+
}
|
|
1286
|
+
};
|
|
1287
|
+
/**
|
|
1288
|
+
* The MIME type that is being used for recording.
|
|
1289
|
+
* @type {string}
|
|
1290
|
+
*/
|
|
1291
|
+
MediaRecorder$1.prototype.mimeType = "audio/wav";
|
|
1292
|
+
/**
|
|
1293
|
+
* Returns `true` if the MIME type specified is one the polyfill can record.
|
|
1294
|
+
*
|
|
1295
|
+
* This polyfill supports `audio/wav` and `audio/mpeg`.
|
|
1296
|
+
*
|
|
1297
|
+
* @param {string} mimeType The mimeType to check.
|
|
1298
|
+
*
|
|
1299
|
+
* @return {boolean} `true` on `audio/wav` and `audio/mpeg` MIME type.
|
|
1300
|
+
*/
|
|
1301
|
+
MediaRecorder$1.isTypeSupported = (mimeType) => {
|
|
1302
|
+
return MediaRecorder$1.prototype.mimeType === mimeType;
|
|
1303
|
+
};
|
|
1304
|
+
/**
|
|
1305
|
+
* `true` if MediaRecorder can not be polyfilled in the current browser.
|
|
1306
|
+
* @type {boolean}
|
|
1307
|
+
*
|
|
1308
|
+
* @example
|
|
1309
|
+
* if (MediaRecorder.notSupported) {
|
|
1310
|
+
* showWarning('Audio recording is not supported in this browser')
|
|
1311
|
+
* }
|
|
1312
|
+
*/
|
|
1313
|
+
MediaRecorder$1.notSupported = !navigator.mediaDevices || !AudioContext;
|
|
1314
|
+
/**
|
|
1315
|
+
* Converts RAW audio buffer to compressed audio files.
|
|
1316
|
+
* It will be loaded to Web Worker.
|
|
1317
|
+
* By default, WAVE encoder will be used.
|
|
1318
|
+
* @type {function}
|
|
1319
|
+
*
|
|
1320
|
+
* @example
|
|
1321
|
+
* MediaRecorder.prototype.mimeType = 'audio/ogg'
|
|
1322
|
+
* MediaRecorder.encoder = oggEncoder
|
|
1323
|
+
*/
|
|
1324
|
+
MediaRecorder$1.encoder = wave_encoder_default;
|
|
1325
|
+
var audio_recorder_polyfill_default = MediaRecorder$1;
|
|
1326
|
+
|
|
1327
|
+
//#endregion
|
|
1328
|
+
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/mpeg-encoder/index.js
|
|
1329
|
+
var mpeg_encoder_default = () => {
|
|
1330
|
+
importScripts("https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.0/lame.min.js");
|
|
1331
|
+
let CHANNELS = 1;
|
|
1332
|
+
let KBPS = 128;
|
|
1333
|
+
let DEFAULT_SAMPLE_RATE = 44100;
|
|
1334
|
+
let encoder;
|
|
1335
|
+
let recorded = new Int8Array();
|
|
1336
|
+
function concat(a, b) {
|
|
1337
|
+
if (b.length === 0) return a;
|
|
1338
|
+
let c = new Int8Array(a.length + b.length);
|
|
1339
|
+
c.set(a);
|
|
1340
|
+
c.set(b, a.length);
|
|
1341
|
+
return c;
|
|
1342
|
+
}
|
|
1343
|
+
function init(sampleRate) {
|
|
1344
|
+
encoder = new lamejs.Mp3Encoder(CHANNELS, sampleRate || DEFAULT_SAMPLE_RATE, KBPS);
|
|
1345
|
+
}
|
|
1346
|
+
function encode(buffer) {
|
|
1347
|
+
for (let i = 0; i < buffer.length; i++) buffer[i] = buffer[i] * 32767.5;
|
|
1348
|
+
let buf = encoder.encodeBuffer(buffer);
|
|
1349
|
+
recorded = concat(recorded, buf);
|
|
1350
|
+
}
|
|
1351
|
+
function dump() {
|
|
1352
|
+
let buf = encoder.flush();
|
|
1353
|
+
recorded = concat(recorded, buf);
|
|
1354
|
+
let buffer = recorded.buffer;
|
|
1355
|
+
recorded = new Int8Array();
|
|
1356
|
+
postMessage(buffer, [buffer]);
|
|
1357
|
+
}
|
|
1358
|
+
onmessage = (e) => {
|
|
1359
|
+
if (e.data[0] === "init") init(e.data[1]);
|
|
1360
|
+
else if (e.data[0] === "encode") encode(e.data[1]);
|
|
1361
|
+
else dump(e.data[1]);
|
|
1362
|
+
};
|
|
1363
|
+
};
|
|
1364
|
+
|
|
1365
|
+
//#endregion
|
|
1366
|
+
//#region ../infra/src/media/blobEncoding.ts
|
|
1367
|
+
/** Encodes a blob as raw base64 bytes (no `data:...;base64,` prefix). */
|
|
1368
|
+
function blobToRawBase64(blob) {
|
|
1369
|
+
return new Promise((resolve, reject) => {
|
|
1370
|
+
const reader = new FileReader();
|
|
1371
|
+
reader.onloadend = () => {
|
|
1372
|
+
const result = reader.result;
|
|
1373
|
+
if (typeof result !== "string") {
|
|
1374
|
+
reject(/* @__PURE__ */ new Error("Failed to encode blob as base64"));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const commaIndex = result.indexOf(",");
|
|
1378
|
+
resolve(commaIndex >= 0 ? result.slice(commaIndex + 1) : result);
|
|
1379
|
+
};
|
|
1380
|
+
reader.onerror = () => {
|
|
1381
|
+
reject(reader.error ?? /* @__PURE__ */ new Error("Failed to read blob"));
|
|
1382
|
+
};
|
|
1383
|
+
reader.readAsDataURL(blob);
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
//#endregion
|
|
1388
|
+
//#region ../infra/src/providers/browser/VoiceRecordingProvider.ts
|
|
1389
|
+
const AudioRecorderPolyfill = audio_recorder_polyfill_default;
|
|
1390
|
+
AudioRecorderPolyfill.encoder = mpeg_encoder_default;
|
|
1391
|
+
AudioRecorderPolyfill.prototype.mimeType = "audio/mpeg";
|
|
1392
|
+
const MIME_TYPE = "audio/mpeg";
|
|
1393
|
+
/**
|
|
1394
|
+
* Records the audio of a stream and produces a single MP3 blob. Used by the
|
|
1395
|
+
* Video Selfie voice-consent step to capture the spoken consent phrase
|
|
1396
|
+
* independently of the main video recording (which keeps running on its own
|
|
1397
|
+
* recorder).
|
|
1398
|
+
*
|
|
1399
|
+
* Records from a clone of the supplied stream so stopping this recorder never
|
|
1400
|
+
* tears down tracks the main recording is still using.
|
|
1401
|
+
*/
|
|
1402
|
+
var VoiceRecordingProvider = class {
|
|
1403
|
+
constructor() {
|
|
1404
|
+
this.recorder = null;
|
|
1405
|
+
this.chunks = [];
|
|
1406
|
+
this._isRecording = false;
|
|
1407
|
+
}
|
|
1408
|
+
get isRecording() {
|
|
1409
|
+
return this._isRecording;
|
|
1410
|
+
}
|
|
1411
|
+
start(stream) {
|
|
1412
|
+
this.reset();
|
|
1413
|
+
this.chunks = [];
|
|
1414
|
+
const recorder = new AudioRecorderPolyfill(new MediaStream(stream.getAudioTracks()).clone());
|
|
1415
|
+
recorder.mimeType = MIME_TYPE;
|
|
1416
|
+
recorder.addEventListener("dataavailable", (event) => {
|
|
1417
|
+
if (event.data.size > 0) this.chunks.push(event.data);
|
|
1418
|
+
});
|
|
1419
|
+
recorder.start();
|
|
1420
|
+
this.recorder = recorder;
|
|
1421
|
+
this._isRecording = true;
|
|
1422
|
+
}
|
|
1423
|
+
stop() {
|
|
1424
|
+
const recorder = this.recorder;
|
|
1425
|
+
return new Promise((resolve) => {
|
|
1426
|
+
if (!recorder || !this._isRecording) {
|
|
1427
|
+
resolve(new Blob([], { type: MIME_TYPE }));
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
recorder.addEventListener("stop", () => {
|
|
1431
|
+
this._isRecording = false;
|
|
1432
|
+
recorder.stream?.getTracks().forEach((track) => track.stop());
|
|
1433
|
+
this.recorder = null;
|
|
1434
|
+
resolve(new Blob(this.chunks, { type: MIME_TYPE }));
|
|
1435
|
+
});
|
|
1436
|
+
recorder.stop();
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
async stopAsBase64() {
|
|
1440
|
+
const blob = await this.stop();
|
|
1441
|
+
if (blob.size === 0) return "";
|
|
1442
|
+
return blobToRawBase64(blob);
|
|
1443
|
+
}
|
|
1444
|
+
reset() {
|
|
1445
|
+
const recorder = this.recorder;
|
|
1446
|
+
if (recorder && this._isRecording) {
|
|
1447
|
+
try {
|
|
1448
|
+
recorder.stop();
|
|
1449
|
+
} catch {}
|
|
1450
|
+
recorder.stream?.getTracks().forEach((track) => track.stop());
|
|
1451
|
+
}
|
|
1452
|
+
this.recorder = null;
|
|
1453
|
+
this._isRecording = false;
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
|
|
1457
|
+
//#endregion
|
|
1458
|
+
//#region src/modules/video-selfie/videoSelfieConsentRecorder.ts
|
|
1459
|
+
function createConsentRecorder() {
|
|
1460
|
+
const provider = new VoiceRecordingProvider();
|
|
1461
|
+
return {
|
|
1462
|
+
start(stream) {
|
|
1463
|
+
provider.start(stream);
|
|
1464
|
+
},
|
|
1465
|
+
async submit() {
|
|
1466
|
+
const base64 = await provider.stopAsBase64();
|
|
1467
|
+
if (base64.length === 0) return { success: false };
|
|
1468
|
+
return addSpeech(base64);
|
|
1469
|
+
},
|
|
1470
|
+
cancel() {
|
|
1471
|
+
provider.reset();
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
//#endregion
|
|
1477
|
+
//#region src/modules/video-selfie/videoSelfieFlowModuleMachine.ts
|
|
1478
|
+
/**
|
|
1479
|
+
* Parked flow-module machine for the `VIDEO_ONBOARDING` step.
|
|
1480
|
+
*
|
|
1481
|
+
* The video-selfie UI component owns the entire capture / streaming / upload
|
|
1482
|
+
* lifecycle (it creates its own {@link createVideoSelfieRecordingManager}) and
|
|
1483
|
+
* signals completion to the flow via `flowManager.completeModule()`
|
|
1484
|
+
* (`MODULE_COMPLETE`). This machine therefore only needs to hold the step open:
|
|
1485
|
+
* it starts in a non-final `running` state and never transitions, so the
|
|
1486
|
+
* orchestrated flow stays in `runningModule` until the UI advances it.
|
|
1487
|
+
*
|
|
1488
|
+
* **Completion contract:** the L2 `incode-video-selfie` web component MUST call
|
|
1489
|
+
* `flowManager.completeModule()` once the recording manager reaches `finished`
|
|
1490
|
+
* (success) or `error` (terminal failure). Until that event is sent, the flow
|
|
1491
|
+
* orchestrator remains in `runningModule` — this is intentional.
|
|
1492
|
+
*
|
|
1493
|
+
* **Abort / unmount:** if the user closes the module or the web component
|
|
1494
|
+
* unmounts before completion, the orchestrated flow manager's existing close /
|
|
1495
|
+
* abort path (`MODULE_CLOSE` / session teardown) is responsible for exiting the
|
|
1496
|
+
* step. This parked machine does not model those paths itself.
|
|
1497
|
+
*
|
|
1498
|
+
* Contrast {@link noOpFlowModuleMachine}, whose initial state is `final` — that
|
|
1499
|
+
* is used for headless steps (e.g. `IP_ADDRESS`) that must complete instantly
|
|
1500
|
+
* with no UI. Using a no-op here would skip the video-selfie UI entirely.
|
|
1501
|
+
*/
|
|
1502
|
+
const videoSelfieFlowModuleMachine = setup({}).createMachine({
|
|
1503
|
+
id: "videoSelfieFlowModule",
|
|
1504
|
+
initial: "running",
|
|
1505
|
+
states: { running: {} }
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
//#endregion
|
|
1509
|
+
export { createConsentRecorder, createVideoSelfieCameraWarmer, createVideoSelfieRecordingManager, facingModeForStep, getVideoSelfieQuestions, getVoiceConsentPhrase, resolveVideoSelfieSteps, videoSelfieFlowModuleMachine, videoSelfieWasmPipelines, warmupVideoSelfieWasm };
|