@incodetech/core 2.3.2 → 2.4.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/THIRD_PARTY_NOTICES.txt +1243 -3
- package/dist/Actor-DpTj5PFb.d.ts +1 -0
- package/dist/{BaseWasmProvider-Dyi6dKp1.d.ts → BaseWasmProvider-BL9wIRnN.d.ts} +2 -2
- package/dist/{FaceDetectionProvider-DtmtCKa9.esm.js → FaceDetectionProvider-BjTer9h3.esm.js} +126 -10
- package/dist/HttpRequestError-Bh0pNGSC.esm.js +35 -0
- package/dist/{IIdCaptureCapability-RNiFhvXR.d.ts → IIdCaptureCapability-DBsv9-7M.d.ts} +2 -2
- package/dist/{IWasmUtilCapability-CNWRl7jr.d.ts → IWasmUtilCapability-CPX0RGdQ.d.ts} +28 -5
- package/dist/{Manager-CQQ99QOI.d.ts → Manager-C9T5amUN.d.ts} +7 -1
- package/dist/{MotionSensorProvider-DyhbM4Si.esm.js → MotionSensorProvider-DYxh8TjR.esm.js} +1 -1
- package/dist/{ekycStateMachine-UeJRO8_p.esm.js → PhoneValidationProvider-SZiOS0jB.esm.js} +232 -827
- package/dist/{StreamCanvasCapture-_8gdW5Lk.d.ts → StreamCanvasCapture-DVmzJux5.d.ts} +2 -2
- package/dist/{safeStorage-BdU9_m2E.esm.js → WasmUtilProvider-CIdnE7Fx.esm.js} +95 -46
- package/dist/{addressSearch-DyHGgeX8.esm.js → addressSearch-BiYZjvp3.esm.js} +143 -4
- package/dist/{advanceableErrors-CpF2cBy6.esm.js → advanceableErrors-q_pfU3Cn.esm.js} +5 -4
- package/dist/ae-signature.d.ts +2 -2
- package/dist/ae-signature.esm.js +1 -1
- package/dist/antifraud.d.ts +1 -1
- package/dist/antifraud.esm.js +2 -2
- package/dist/{antifraudManager-BY36bS12.d.ts → antifraudManager-DbGCvlJs.d.ts} +2 -2
- package/dist/{antifraudManager-DNkw66oW.esm.js → antifraudManager-HL08jeDX.esm.js} +3 -3
- package/dist/{antifraudStateMachine-DBeq47D2.esm.js → antifraudStateMachine-DoJVzblM.esm.js} +3 -3
- package/dist/{apiError-CmGEmaGU.esm.js → apiError-DcSWedC6.esm.js} +2 -1
- package/dist/authentication.d.ts +3 -3
- package/dist/authentication.esm.js +2 -2
- package/dist/{authenticationManager-CboRCl_N.d.ts → authenticationManager-CANv5-W5.d.ts} +2 -8
- package/dist/{authenticationManager-BFjSjtQ_.esm.js → authenticationManager-SaiAxqvS.esm.js} +6 -6
- package/dist/{authenticationStateMachine-Dfo34ZiI.esm.js → authenticationStateMachine-CHSxmV7z.esm.js} +5 -6
- package/dist/avatar.d.ts +1 -1
- package/dist/{backCameraStream-B0-YgP_y.esm.js → backCameraStream-CMvXj4X4.esm.js} +1 -1
- package/dist/camera.esm.js +1 -1
- package/dist/certificate-issuance.d.ts +1 -1
- package/dist/certificate-issuance.esm.js +2 -2
- package/dist/{certificateIssuanceManager-C3-CI0_s.esm.js → certificateIssuanceManager-CMg4SgL4.esm.js} +3 -3
- package/dist/{certificateIssuanceManager-DCgEidHz.d.ts → certificateIssuanceManager-Dr8A4Lgr.d.ts} +2 -3
- package/dist/{certificateIssuanceStateMachine-CxqS_nD-.esm.js → certificateIssuanceStateMachine-Cs64QmYH.esm.js} +4 -4
- package/dist/consent.d.ts +1 -1
- package/dist/consent.esm.js +2 -2
- package/dist/{consentManager-B6E1XJDw.esm.js → consentManager-CjrxY737.esm.js} +3 -3
- package/dist/{consentManager-2h_w6wFc.d.ts → consentManager-DgzWQpAR.d.ts} +6 -7
- package/dist/{consentStateMachine-DTSfdGiW.esm.js → consentStateMachine-BCj5Jb3g.esm.js} +3 -3
- package/dist/{createCameraCycleOwnership-DnwTl1ad.esm.js → createCameraCycleOwnership-kdlhkmmY.esm.js} +71 -65
- package/dist/{createFaceAvatar-BlcTYgJv.d.ts → createFaceAvatar-DXgmQmQy.d.ts} +1 -1
- package/dist/cross-document-data-match.d.ts +1 -1
- package/dist/cross-document-data-match.esm.js +1 -1
- package/dist/{crossDocumentDataMatchManager-9XhFVZ5S.esm.js → crossDocumentDataMatchManager-BHnGRsto.esm.js} +4 -4
- package/dist/{crossDocumentDataMatchManager-Dq8Z3SOg.d.ts → crossDocumentDataMatchManager-yCWCOzcU.d.ts} +2 -2
- package/dist/curp-validation.d.ts +1 -1
- package/dist/curp-validation.esm.js +2 -2
- package/dist/{curpValidationManager-DNuZn9fn.d.ts → curpValidationManager-B-1EZbP_.d.ts} +3 -3
- package/dist/{curpValidationManager-DbIMYFcJ.esm.js → curpValidationManager-DBNIn8BF.esm.js} +15 -22
- package/dist/{curpValidationStateMachine-g5kMfuLj.esm.js → curpValidationStateMachine-DAxImy8p.esm.js} +4 -4
- package/dist/custom-fields.d.ts +1 -1
- package/dist/custom-fields.esm.js +1 -1
- package/dist/custom-watchlist.d.ts +1 -1
- package/dist/custom-watchlist.esm.js +2 -2
- package/dist/{customFieldsManager-CzWZTkW-.esm.js → customFieldsManager-BV6UyLFV.esm.js} +4 -4
- package/dist/{customFieldsManager-8-W2KY4n.d.ts → customFieldsManager-CF-cO8ap.d.ts} +2 -2
- package/dist/{customWatchlistManager-DWTED5Ur.esm.js → customWatchlistManager-DML0UPzf.esm.js} +3 -3
- package/dist/{customWatchlistManager-PBWHr_oM.d.ts → customWatchlistManager-DolUswyR.d.ts} +2 -2
- package/dist/{customWatchlistStateMachine-DOQou6TW.esm.js → customWatchlistStateMachine-D9gI5uu7.esm.js} +3 -3
- package/dist/deepsightLoader-DvfAYnzg.esm.js +55 -0
- package/dist/{deepsightLoader-v-dLejS-.esm.js → deepsightService-DLGO9yv1.esm.js} +51 -56
- package/dist/device.esm.js +1 -1
- package/dist/document-capture.d.ts +2 -2
- package/dist/document-capture.esm.js +2 -2
- package/dist/{documentCaptureManager-BRPxqYD2.esm.js → documentCaptureManager-C9XUnYU6.esm.js} +17 -18
- package/dist/{documentCaptureManager-lEtCM320.d.ts → documentCaptureManager-DkyDtm3Y.d.ts} +11 -34
- package/dist/{documentCaptureStateMachine-D312sSIu.esm.js → documentCaptureStateMachine-BHDsCfhL.esm.js} +55 -58
- package/dist/dynamic-forms.d.ts +2 -2
- package/dist/dynamic-forms.esm.js +2 -2
- package/dist/{dynamicFormsManager-C-9Jhnd3.esm.js → dynamicFormsManager-5uIXobxi.esm.js} +3 -3
- package/dist/{dynamicFormsManager-D5R_uxLQ.d.ts → dynamicFormsManager-C0dHs1ty.d.ts} +3 -3
- package/dist/{dynamicFormsStateMachine-DGI_F2PP.esm.js → dynamicFormsStateMachine-DQmok084.esm.js} +4 -4
- package/dist/ekyb.d.ts +5 -5
- package/dist/ekyb.esm.js +3 -3
- package/dist/{ekybManager-BwRuGAwt.d.ts → ekybManager-BaZ_7CEu.d.ts} +4 -4
- package/dist/{ekybManager-DeoMcZEz.esm.js → ekybManager-CazShiQz.esm.js} +23 -27
- package/dist/{ekybStateMachine-D1IJ1Z72.esm.js → ekybStateMachine-hstiCNmW.esm.js} +338 -13
- package/dist/ekyc.d.ts +2 -2
- package/dist/ekyc.esm.js +3 -3
- package/dist/{ekycManager-BX2d46FY.esm.js → ekycManager-BdJRUsz0.esm.js} +29 -36
- package/dist/{ekycManager-DLzDuBzI.d.ts → ekycManager-DW-ynodd.d.ts} +3 -3
- package/dist/ekycStateMachine-x6Klot8g.esm.js +1444 -0
- package/dist/{ekycUtils-D6wfSpPN.esm.js → ekycUtils-zY8CdKJL.esm.js} +53 -3
- package/dist/electronic-signature.d.ts +1 -1
- package/dist/electronic-signature.esm.js +1 -1
- package/dist/{electronicSignatureManager-yYUc30na.esm.js → electronicSignatureManager-Bc8spG8p.esm.js} +10 -10
- package/dist/{electronicSignatureManager-Ds-KEVTq.d.ts → electronicSignatureManager-DTSLOvKR.d.ts} +2 -3
- package/dist/email.d.ts +1 -1
- package/dist/email.esm.js +2 -2
- package/dist/{emailManager-bR1N5rV_.esm.js → emailManager-CtlKRRaR.esm.js} +20 -26
- package/dist/{emailManager-CADXW_9X.d.ts → emailManager-Dy6omJVp.d.ts} +2 -2
- package/dist/{emailStateMachine-Bc_6oIYw.esm.js → emailStateMachine-5Neq3rE5.esm.js} +4 -4
- package/dist/{endpoints-B-3Q-LUw.esm.js → endpoints-DHzlkQNe.esm.js} +2 -0
- package/dist/{events-ijlbX_Wx.esm.js → events-CAgFE-k3.esm.js} +19 -7
- package/dist/events.d.ts +3 -2
- package/dist/events.esm.js +1 -1
- package/dist/extensibility.d.ts +38 -38
- package/dist/extensibility.esm.js +36 -36
- package/dist/{externalFetch-BKBThAUC.esm.js → externalFetch-DrR9xshk.esm.js} +3 -2
- package/dist/face-match.d.ts +1 -1
- package/dist/face-match.esm.js +2 -2
- package/dist/{faceCaptureManagerFactory-m-7ae67q.d.ts → faceCaptureManagerFactory-jKQ2QtY-.d.ts} +11 -51
- package/dist/{faceCaptureManagerFactory-B6i9QcAk.esm.js → faceCaptureManagerFactory-uFm1AcaY.esm.js} +19 -54
- package/dist/{faceCaptureSetup-DU1mjRr6.esm.js → faceCaptureSetup-Cu2wabzo.esm.js} +99 -395
- package/dist/{faceMatchManager-Bmv0HzuY.esm.js → faceMatchManager-9Nj9aPci.esm.js} +3 -3
- package/dist/{faceMatchManager-BnvrZM2F.d.ts → faceMatchManager-DIgG-UFS.d.ts} +1 -2
- package/dist/{faceMatchStateMachine-mRIHQ3dA.esm.js → faceMatchStateMachine-B9ZUnlyU.esm.js} +6 -4
- package/dist/{feature-management-C8CuN0os.esm.js → feature-management-BTbYruNe.esm.js} +3 -3
- package/dist/feature-management.esm.js +1 -1
- package/dist/field-comparison.d.ts +2 -2
- package/dist/field-comparison.esm.js +1 -1
- package/dist/{fieldComparisonManager-CBvEGxFx.d.ts → fieldComparisonManager-CUX4gza2.d.ts} +6 -3
- package/dist/{fieldComparisonManager-CC-klG0h.esm.js → fieldComparisonManager-Dk38hXZP.esm.js} +35 -17
- package/dist/fiscal-qr.d.ts +1 -1
- package/dist/fiscal-qr.esm.js +1 -1
- package/dist/{fiscalQrManager-B1STmT6z.d.ts → fiscalQrManager-D0Iy7EKR.d.ts} +2 -2
- package/dist/fiscalQrManager-DmBkQfs0.esm.js +6798 -0
- package/dist/flow-events.d.ts +1 -1
- package/dist/flow.d.ts +14 -13
- package/dist/flow.esm.js +14 -14
- package/dist/{flowCompletionService-DccgtDnm.esm.js → flowCompletionService-xnn_SlJU.esm.js} +2 -2
- package/dist/{flowServices-CdsWsppG.esm.js → flowServices-DA64AEZC.esm.js} +11 -7
- package/dist/geolocation.d.ts +1 -1
- package/dist/geolocation.esm.js +2 -2
- package/dist/{geolocationManager-CGHBxtMb.esm.js → geolocationManager-CqNLY45e.esm.js} +6 -7
- package/dist/{geolocationManager-Cz744E5S.d.ts → geolocationManager-Dn24g1fl.d.ts} +2 -3
- package/dist/{geolocationStateMachine-CdEb1Gn0.esm.js → geolocationStateMachine-D-cQi7ar.esm.js} +4 -4
- package/dist/{getDeviceClass-5NYZg9s6.esm.js → getDeviceClass-bsWdEDxP.esm.js} +1 -1
- package/dist/{getParsedUserAgent-B2iFDql1.esm.js → getParsedUserAgent-DhQPeKqw.esm.js} +1 -1
- package/dist/government-validation.d.ts +1 -1
- package/dist/government-validation.esm.js +2 -2
- package/dist/{governmentValidationManager-D02vT4yY.d.ts → governmentValidationManager-B82zOut8.d.ts} +3 -3
- package/dist/{governmentValidationManager-CacFOtKO.esm.js → governmentValidationManager-CP0SiOOe.esm.js} +3 -3
- package/dist/{governmentValidationStateMachine-CoaZOSTP.esm.js → governmentValidationStateMachine-C3YQ8yCZ.esm.js} +3 -3
- package/dist/home.d.ts +3 -26
- package/dist/home.esm.js +2 -2
- package/dist/id-ocr.d.ts +1 -1
- package/dist/id-ocr.esm.js +2 -2
- package/dist/id-selfie-flow.d.ts +6 -9
- package/dist/id-selfie-flow.esm.js +23 -28
- package/dist/id-verification.d.ts +1 -1
- package/dist/id-verification.esm.js +2 -2
- package/dist/id.d.ts +4 -4
- package/dist/id.esm.js +4 -205
- package/dist/{idCaptureDefaults-Dl64Trv5.esm.js → idCaptureDefaults-CBg73RnV.esm.js} +8 -5
- package/dist/{idCaptureManager-BHwL2Z0V.d.ts → idCaptureManager-BY97SRCV.d.ts} +242 -98
- package/dist/{idCaptureManager-BNmVJQkd.esm.js → idCaptureManager-D4V8vXtY.esm.js} +276 -210
- package/dist/{idCaptureServices-dtDG2HXS.esm.js → idCaptureServices-DJ33k3-p.esm.js} +21 -39
- package/dist/{idCaptureStateMachine-BbiZD3uf.esm.js → idCaptureStateMachine-PJt0yzom.esm.js} +989 -872
- package/dist/{idOcrManager-CFlDwUvN.d.ts → idOcrManager-BTdHlJ9D.d.ts} +2 -3
- package/dist/{idOcrManager-ByWuQ4ej.esm.js → idOcrManager-DxoCesYP.esm.js} +3 -3
- package/dist/{idOcrStateMachine-Bt_abXQQ.esm.js → idOcrStateMachine-kPn7DB2I.esm.js} +3 -3
- package/dist/{idVerificationManager-BB4Pj_N6.d.ts → idVerificationManager-BJapZfpy.d.ts} +2 -3
- package/dist/{idVerificationManager-DT42c77H.esm.js → idVerificationManager-CDhRUU5n.esm.js} +3 -3
- package/dist/{idVerificationStateMachine-DbDVIh9t.esm.js → idVerificationStateMachine-0HTVVxse.esm.js} +3 -3
- package/dist/identity-reuse.d.ts +2 -2
- package/dist/identity-reuse.esm.js +2 -2
- package/dist/{identityReuseManager-yG3acUrQ.esm.js → identityReuseManager-BleUi6TK.esm.js} +26 -13
- package/dist/{identityReuseManager-BXEBBI3N.d.ts → identityReuseManager-ooAbp6Kc.d.ts} +164 -46
- package/dist/{identityReuseStateMachine-BhAkuIra.esm.js → identityReuseStateMachine-BrBUpfn5.esm.js} +89 -23
- package/dist/{index-CeaC32qI.d.ts → index-DfEuu-Ew.d.ts} +2 -2
- package/dist/index-DpTj5PFb.d.ts +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/{invokeOnCaptureCallback-BjzPjJ_p.esm.js → invokeOnCaptureCallback-BW7V3vub.esm.js} +1 -1
- package/dist/lamejs-B6TzhU5q.esm.js +10 -0
- package/dist/mandatory-consent.d.ts +2 -2
- package/dist/mandatory-consent.esm.js +2 -2
- package/dist/{mandatoryConsentManager-7dI0D08q.esm.js → mandatoryConsentManager-CX_Dyht7.esm.js} +3 -3
- package/dist/{mandatoryConsentManager-DFVm_i2B.d.ts → mandatoryConsentManager-TkX57U6f.d.ts} +3 -4
- package/dist/{mandatoryConsentStateMachine-DC5Mo6M1.esm.js → mandatoryConsentStateMachine-CoaSB7P7.esm.js} +3 -3
- package/dist/{multipartRecordingService-mwKFTlhR.esm.js → multipartRecordingService-DbeQpXdU.esm.js} +2 -2
- package/dist/{permissionGuards-D6TfVop2.esm.js → permissionGuards-Bf4LbHjC.esm.js} +1 -1
- package/dist/{permissionServices-EY3_Vx6c.esm.js → permissionServices-d2UW0r_L.esm.js} +34 -3
- package/dist/phone-number.d.ts +24 -0
- package/dist/phone-number.esm.js +1922 -0
- package/dist/phone.d.ts +1 -1
- package/dist/phone.esm.js +2 -2
- package/dist/{phoneManager-IjhSv5XS.esm.js → phoneManager-BGBcpqsk.esm.js} +31 -22
- package/dist/{phoneManager-BBU8Lq0O.d.ts → phoneManager-BO-LTih5.d.ts} +16 -3
- package/dist/{phoneStateMachine-CXdXufKg.esm.js → phoneStateMachine-CEm2y7yG.esm.js} +39 -20
- package/dist/phoneValidation-DnBcNUNh.esm.js +48 -0
- package/dist/qe-signature.d.ts +2 -2
- package/dist/qe-signature.esm.js +1 -1
- package/dist/redirect-to-mobile.d.ts +1 -1
- package/dist/redirect-to-mobile.esm.js +2 -2
- package/dist/{redirectToMobileManager-FLEWJtv8.esm.js → redirectToMobileManager-B1yQHFLu.esm.js} +4 -4
- package/dist/{redirectToMobileManager-BD2PvT5B.d.ts → redirectToMobileManager-CVsvpVbQ.d.ts} +2 -2
- package/dist/{redirectToMobileStateMachine-CSWWgUTO.esm.js → redirectToMobileStateMachine--Sr9NEVT.esm.js} +5 -5
- package/dist/{rolldown-runtime-FDOR9p9I.esm.js → rolldown-runtime-B-1-B7_t.esm.js} +10 -1
- package/dist/{runChildModule-B2Y_hlix.esm.js → runChildModule-vdxQMCmO.esm.js} +13 -12
- package/dist/safeStorage-UxDrnjwa.esm.js +35 -0
- package/dist/{sdkVersion-N1UiyFiY.esm.js → sdkVersion-F5YZbjip.esm.js} +1 -1
- package/dist/selfie.d.ts +4 -4
- package/dist/selfie.esm.js +12 -12
- package/dist/selfieErrorUtils-D-tsgEuX.esm.js +21 -0
- package/dist/{selfieManager-BSZG891S.d.ts → selfieManager-1J0IEyFq.d.ts} +2 -8
- package/dist/{selfieManager-AS6wfUvZ.esm.js → selfieManager-26pENXxw.esm.js} +6 -6
- package/dist/selfieStateMachine-BhfKsl3I.esm.js +41 -0
- package/dist/{session-BESZc05k.esm.js → session-Bu7djCsj.esm.js} +5 -5
- package/dist/session.d.ts +15 -3
- package/dist/session.esm.js +30 -5
- package/dist/sessionInitializer-jxbfCnE-.esm.js +974 -0
- package/dist/{setup-BuPbxpwF.esm.js → setup-C38QkrSP.esm.js} +232 -112
- package/dist/{setup-Cyko6cAn.d.ts → setup-lgkQ8dJW.d.ts} +20 -11
- package/dist/signature.d.ts +1 -1
- package/dist/signature.esm.js +2 -2
- package/dist/{signatureManager-DGBSWz2_.d.ts → signatureManager-BpVt_Jv7.d.ts} +2 -2
- package/dist/{signatureManager-B72np4ZO.esm.js → signatureManager-qqXyH93e.esm.js} +7 -11
- package/dist/{signatureStateMachine-ce_XMA3l.esm.js → signatureStateMachine-BkRSrkKp.esm.js} +3 -3
- package/dist/splitTrackedName-3zJRd2RD.esm.js +19 -0
- package/dist/{src-35ekGRzK.esm.js → src-DJAJjRYQ.esm.js} +1 -1
- package/dist/{stats-CZcy2J53.esm.js → stats-DmG_tkGk.esm.js} +1 -1
- package/dist/stats.esm.js +1 -1
- package/dist/{sessionInitializer-CnhAkMUN.esm.js → thumbmark.esm-CRc0cYvz.esm.js} +33 -776
- package/dist/{trueSightService-M5VpCo96.esm.js → trueSightService-BWgRVGIT.esm.js} +893 -900
- package/dist/trust-graph.d.ts +1 -2
- package/dist/trust-graph.esm.js +2 -2
- package/dist/{types-C0oWGUzg.d.ts → types-0L-eFDmr.d.ts} +1 -1
- package/dist/{types-C1g9itHE.d.ts → types-BGXxRwQM.d.ts} +6 -7
- package/dist/{types-BfgmV7s7.d.ts → types-BQA7lRDV.d.ts} +1 -1
- package/dist/{types-CxGdmzxa.d.ts → types-BvFmYX5c.d.ts} +1 -1
- package/dist/{types-Cx-1zo-i.d.ts → types-BvLo3U4u.d.ts} +1 -1
- package/dist/types-C71Zo6vl.d.ts +6 -0
- package/dist/{types-DEGVXwsk2.d.ts → types-CyPUIpzf.d.ts} +110 -62
- package/dist/{types-Bx-BWveD.d.ts → types-DTYr9pCB.d.ts} +1 -1
- package/dist/{types-BjSly8cp.d.ts → types-DUnFZ08M.d.ts} +1 -1
- package/dist/{types-ESdtnHtg.d.ts → types-zpO4fU6a.d.ts} +3 -4
- package/dist/{urlUtils-D6phjq1-.esm.js → urlUtils-6jsE6rKp.esm.js} +9 -1
- package/dist/video-selfie.d.ts +6 -9
- package/dist/video-selfie.esm.js +1386 -1344
- package/dist/{videoSelfieCameraWarmer-q9ZjvXu9.esm.js → videoSelfieCameraWarmer-C366RKqu.esm.js} +11 -11
- package/dist/{videoSelfieFlowModuleMachine-BUe2uACW.esm.js → videoSelfieFlowModuleMachine-BCFVHBQ1.esm.js} +1 -1
- package/dist/{videoSelfieStepDetector-ChYgFM15.d.ts → videoSelfieStepDetector-C5QHOvWb.d.ts} +4 -4
- package/dist/videoTrimByCopy-BFfAiNpv.esm.js +15901 -0
- package/dist/{warmup-Cis2p6O0.d.ts → warmup-BUPPRAA1.d.ts} +7 -0
- package/dist/{warmup-ChDwHSel.esm.js → warmup-DMtbyb2l.esm.js} +27 -9
- package/dist/wasm.d.ts +2 -2
- package/dist/wasm.esm.js +4 -4
- package/dist/{wasmDsFlagSync-DYrLUEII.esm.js → wasmDsFlagSync-BXCGqP48.esm.js} +1 -1
- package/dist/{wasmWebClient-B7baE-Wf.esm.js → wasmWebClient-kbYZ2sxw.esm.js} +259 -20
- package/dist/watchlist-for-business.d.ts +1 -1
- package/dist/watchlist-for-business.esm.js +2 -2
- package/dist/watchlist.d.ts +1 -1
- package/dist/watchlist.esm.js +2 -2
- package/dist/{watchlistForBusinessManager-CsnJnG-C.d.ts → watchlistForBusinessManager-B3GDPZrr.d.ts} +2 -2
- package/dist/{watchlistForBusinessManager-CIMkpEhu.esm.js → watchlistForBusinessManager-BvF4jK3S.esm.js} +7 -5
- package/dist/{watchlistForBusinessStateMachine-CJ-MvUre.esm.js → watchlistForBusinessStateMachine-121XK-Et.esm.js} +3 -3
- package/dist/{watchlistManager-CixcLNDT.esm.js → watchlistManager-CNWEOYSq.esm.js} +3 -3
- package/dist/{watchlistManager-DLXef90v.d.ts → watchlistManager-DJQOd8Pw.d.ts} +2 -2
- package/dist/{watchlistStateMachine-CBqk2w-h.esm.js → watchlistStateMachine-Dgfm42w2.esm.js} +3 -3
- package/dist/workflow.d.ts +34 -5
- package/dist/workflow.esm.js +64 -46
- package/dist/{xstate-actors.esm-BXeQ-Ucz.esm.js → xstate-actors.esm-8Gzepa_A.esm.js} +1 -1
- package/dist/{xstate.esm-BuFLFuzz.esm.js → xstate.esm-DSajW7CJ.esm.js} +24 -10
- package/package.json +13 -5
- package/dist/Actor-iTq7YY48.d.ts +0 -1
- package/dist/StateMachine-EDpYWy0i.d.ts +0 -6
- package/dist/fiscalQrManager-DbCVMIBx.esm.js +0 -34143
- package/dist/index-B8weJxmL.d.ts +0 -1
- package/dist/selfieErrorUtils-UiQ46EBa.esm.js +0 -116
- package/dist/selfieStateMachine-DY7vJVNY.esm.js +0 -71
- package/dist/types-BN5RPPIZ.esm.js +0 -34
- package/dist/types-Bt9RuRqt.d.ts +0 -6
- package/dist/virtualCameraCheck-Cjab7FTI.esm.js +0 -48
- /package/dist/{types-BSBzhHRH.d.ts → types-BSBzhHRH2.d.ts} +0 -0
package/dist/video-selfie.esm.js
CHANGED
|
@@ -1,1389 +1,1431 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as ensureWasmPipelines } from "./setup-BuPbxpwF.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
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
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-
|
|
11
|
-
import { t as VIDEO_SELFIE_VALIDATION_ATTEMPTS_EXHAUSTED_CODE } from "./advanceableErrors-
|
|
12
|
-
import { a as isConnectionError, n as defaultAcquireCamera, r as createDefaultStepDetector, t as createVideoSelfieCameraWarmer } from "./videoSelfieCameraWarmer-
|
|
13
|
-
import { t as videoSelfieFlowModuleMachine } from "./videoSelfieFlowModuleMachine-
|
|
14
|
-
//#region
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return
|
|
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
|
-
*
|
|
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
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
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/
|
|
846
|
-
function
|
|
847
|
-
|
|
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/
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
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
|
|
907
|
-
return
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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
|
-
*
|
|
930
|
-
*
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
*
|
|
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
|
|
937
|
-
const
|
|
938
|
-
|
|
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
|
-
|
|
952
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
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
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
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
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
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
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
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
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
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
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
1236
|
-
|
|
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
|
-
* `
|
|
1240
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
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
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
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
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
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
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
return
|
|
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() {
|