@incodetech/core 2.1.1 → 2.2.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/dist/{3dAvatarProvider-BwOmTMOy.esm.js → 3dAvatarProvider-g02DTFqT.esm.js} +811 -880
- package/dist/Actor-iTq7YY48.d.ts +1 -0
- package/dist/BrowserDiagnosticsCryptoProvider-CriIh1Gt.esm.js +95 -0
- package/dist/BrowserLazyImportProvider-BhoA957O.esm.js +114 -0
- package/dist/{BrowserStorageProvider-CBHBhKQN.esm.js → BrowserStorageProvider-k-FDkC4W.esm.js} +2 -4
- package/dist/{BrowserTimerProvider-CHloo0eQ.esm.js → BrowserTimerProvider-BXhgNxLt.esm.js} +1 -2
- package/dist/{BrowserVisibilityObserverProvider-GBS6XSii.esm.js → BrowserVisibilityObserverProvider-BX1BiLDK.esm.js} +1 -2
- package/dist/Face2DProvider-UUQt_vRC.esm.js +437 -0
- package/dist/{createFaceAvatar-CpdYp9Iy.d.ts → FaceDetectionProvider-Mh6q3EbA.d.ts} +107 -159
- package/dist/IBrowserEnvironmentCapability-DEaRRk-R.esm.js +9 -0
- package/dist/IFaceAvatarCapability-BrL19G0s.d.ts +23 -0
- package/dist/IFeatureManagementCapability-DdEUp6tB.d.ts +1125 -0
- package/dist/IIdCaptureCapability-DHTT3FC2.d.ts +237 -0
- package/dist/IStorageCapability-ORkQfK3F.d.ts +242 -0
- package/dist/{ITimerCapability-DJ73P4RK.esm.js → ITimerCapability-DgdDzp8G.esm.js} +1 -2
- package/dist/{Manager-goNPc_h_.d.ts → Manager-CQQ99QOI.d.ts} +1 -0
- package/dist/{MotionSensorProvider-BHuHoYvJ.esm.js → MotionSensorProvider-DGiV77J_.esm.js} +2 -37
- package/dist/{OpenViduRecordingProvider-CK6A5kiU.esm.js → OpenViduRecordingProvider-Du98qevC.esm.js} +2 -4
- package/dist/PrivacyLensProvider-D8PS7AJa.esm.js +405 -0
- package/dist/StateMachine-EDpYWy0i.d.ts +6 -0
- package/dist/{StreamCanvasCapture-CAqUdVkM.esm.js → StreamCanvasCapture-D2XcXCZ4.esm.js} +2 -4
- package/dist/{addressSearch-D7msRieq.esm.js → addressSearch-DQyrvDQj.esm.js} +10 -21
- package/dist/ae-signature.d.ts +4 -6
- package/dist/ae-signature.esm.js +10 -11
- package/dist/antifraud.d.ts +1 -6
- package/dist/antifraud.esm.js +3 -8
- package/dist/{antifraudManager-5KA84wtF.esm.js → antifraudManager-B8Plzf7a.esm.js} +11 -8
- package/dist/{antifraudManager-DCiNI6np.d.ts → antifraudManager-BY36bS12.d.ts} +3 -3
- package/dist/{antifraudStateMachine-BQVw46N5.esm.js → antifraudStateMachine-r4VxSBS4.esm.js} +5 -7
- package/dist/api-Bx3gKW_y.esm.js +129 -0
- package/dist/{apiError-DkOXfFBu.esm.js → apiError-DZfAyExM.esm.js} +25 -3
- package/dist/authentication.d.ts +3 -19
- package/dist/authentication.esm.js +3 -35
- package/dist/{authenticationManager-BeT6tNZm.d.ts → authenticationManager-BBMGTcwW.d.ts} +3 -4
- package/dist/{authenticationManager-BryLnSaF.esm.js → authenticationManager-DscbQwIl.esm.js} +7 -10
- package/dist/{authenticationStateMachine-DbdxfGHx.esm.js → authenticationStateMachine-SyuM00Fa.esm.js} +11 -15
- package/dist/avatar.d.ts +2 -2
- package/dist/avatar.esm.js +2 -3
- package/dist/avatar2dWarm-DqucNFX4.esm.js +1330 -0
- package/dist/avatarStats-CdUOb3VD.esm.js +136 -0
- package/dist/{backCameraStream-ChrMTJv-.esm.js → backCameraStream-CUeYm5eu.esm.js} +20 -8
- package/dist/browser-DlFgHpoC-DnyrCR8r.esm.js +1513 -0
- package/dist/callback-645_omgz.d.ts +133 -0
- package/dist/camera-_2sXMXiF.esm.js +140 -0
- package/dist/camera.d.ts +19 -2
- package/dist/camera.esm.js +2 -6
- package/dist/{canvas-Pi10hG_l.esm.js → canvas-E1zGkel4.esm.js} +1 -2
- package/dist/certificate-issuance.d.ts +34 -39
- package/dist/certificate-issuance.esm.js +4 -11
- package/dist/{certificateIssuanceStateMachine-DlZQT1_u.esm.js → certificateIssuanceStateMachine-COd7_l2T.esm.js} +7 -11
- package/dist/consent.d.ts +1 -4
- package/dist/consent.esm.js +3 -8
- package/dist/{consentManager-BwNu-uC-.esm.js → consentManager-BApTlfA0.esm.js} +5 -7
- package/dist/{consentManager-B4PMGXW-.d.ts → consentManager-uhDPdU6i.d.ts} +55 -61
- package/dist/{consentStateMachine-BOkVFiU7.esm.js → consentStateMachine-WD-S-Yhs.esm.js} +5 -7
- package/dist/{cpf-16m_cGAM.esm.js → cpf-BIADjddF.esm.js} +1 -2
- package/dist/createFaceAvatar-BEj18KXi.d.ts +50 -0
- package/dist/createFaceAvatar-DQKaRise.esm.js +103 -0
- package/dist/cross-document-data-match.d.ts +2 -26
- package/dist/cross-document-data-match.esm.js +2 -71
- package/dist/crossDocumentDataMatchManager-Dq8Z3SOg.d.ts +41 -0
- package/dist/crossDocumentDataMatchManager-OmYvyung.esm.js +84 -0
- package/dist/curp-validation.d.ts +1 -11
- package/dist/curp-validation.esm.js +3 -8
- package/dist/{curpValidationManager-CoqODZx5.d.ts → curpValidationManager-DVGr7Fq7.d.ts} +4 -6
- package/dist/{curpValidationManager-Cbji2inV.esm.js → curpValidationManager-UZxcrsrz.esm.js} +11 -7
- package/dist/{curpValidationStateMachine-Dlw4qfFb.esm.js → curpValidationStateMachine-9KwnqeoB.esm.js} +22 -14
- package/dist/custom-fields.d.ts +2 -5
- package/dist/custom-fields.esm.js +24 -11
- package/dist/custom-watchlist.d.ts +1 -3
- package/dist/custom-watchlist.esm.js +11 -10
- package/dist/{customWatchlistStateMachine-D8w0mNxm.esm.js → customWatchlistStateMachine-B_ey3TFj.esm.js} +5 -7
- package/dist/{dateUtils-BW6mBV6p.esm.js → dateUtils-CvG0FvK8.esm.js} +1 -2
- package/dist/{deepsightService-CViys6T4.esm.js → deepsightLoader-DcWjMC6A.esm.js} +68 -14
- package/dist/{deepsightService-BuTOFTr8.d.ts → deepsightService-DLVQS_RH.d.ts} +41 -2
- package/dist/device.d.ts +10 -2
- package/dist/device.esm.js +21 -21
- package/dist/document-capture.d.ts +3 -1816
- package/dist/document-capture.esm.js +3 -245
- package/dist/document-upload.d.ts +48 -55
- package/dist/document-upload.esm.js +8 -14
- package/dist/documentCaptureManager-B4Kivr8_.d.ts +2062 -0
- package/dist/documentCaptureManager-DszmcU-l.esm.js +296 -0
- package/dist/{documentCaptureStateMachine-BkTvU3yq.esm.js → documentCaptureStateMachine-D2jBwwVb.esm.js} +66 -40
- package/dist/dynamic-forms.d.ts +2 -4
- package/dist/dynamic-forms.esm.js +3 -9
- package/dist/{dynamicFormsManager-CHXUBNs4.esm.js → dynamicFormsManager-BoZ2-OUI.esm.js} +48 -9
- package/dist/{dynamicFormsManager-CKZkE7Wo.d.ts → dynamicFormsManager-fhK0yINk.d.ts} +22 -46
- package/dist/{dynamicFormsStateMachine-DJ1HGZff.esm.js → dynamicFormsStateMachine-D0RzkoEV.esm.js} +161 -34
- package/dist/ekyb.d.ts +5 -137
- package/dist/ekyb.esm.js +4 -179
- package/dist/ekybManager-DTvfxAOZ.esm.js +178 -0
- package/dist/ekybManager-Dzswb_b8.d.ts +114 -0
- package/dist/{ekybStateMachine-D9GzA4MW.esm.js → ekybStateMachine-BlTIozb6.esm.js} +16 -193
- package/dist/ekyc.d.ts +2 -79
- package/dist/ekyc.esm.js +4 -154
- package/dist/ekycManager-BFpHbBoT.d.ts +97 -0
- package/dist/ekycManager-oTstTJuL.esm.js +156 -0
- package/dist/{ekycStateMachine-DnVRiu3s.esm.js → ekycStateMachine-DHvksnrr.esm.js} +141 -423
- package/dist/ekycUtils-Dew1rBG0.esm.js +223 -0
- package/dist/electronic-signature.d.ts +3 -5
- package/dist/electronic-signature.esm.js +2 -10
- package/dist/{index-36QX_21w.d.ts → electronicSignatureManager-CcUXQZS7.d.ts} +220 -251
- package/dist/{electronicSignatureManager-DESRgObX.esm.js → electronicSignatureManager-DRYlHPjT.esm.js} +47 -33
- package/dist/email.d.ts +1 -5
- package/dist/email.esm.js +3 -10
- package/dist/{emailManager-CMS4q_r6.d.ts → emailManager-CADXW_9X.d.ts} +4 -7
- package/dist/{emailManager-B9Vrv3Ma.esm.js → emailManager-tE-YvNtS.esm.js} +76 -12
- package/dist/{emailStateMachine-CbmKFVSi.esm.js → emailStateMachine-Bm894MHB.esm.js} +7 -9
- package/dist/{endpoints-CwrFQfW7.esm.js → endpoints-CCzL3XD_.esm.js} +55 -6
- package/dist/{events-1wxZ97yf.esm.js → events-CxfNJVZW.esm.js} +43 -176
- package/dist/events.d.ts +28 -10
- package/dist/events.esm.js +2 -4
- package/dist/extensibility.d.ts +37 -37
- package/dist/extensibility.esm.js +31 -80
- package/dist/{externalFetch-hvmz_npP.esm.js → externalFetch-BKBThAUC.esm.js} +2 -4
- package/dist/face-match.d.ts +2 -225
- package/dist/face-match.esm.js +3 -173
- package/dist/{faceCaptureManagerFactory-C9TiGbkA.esm.js → faceCaptureManagerFactory-BDAsBCkh.esm.js} +52 -31
- package/dist/{faceCaptureManagerFactory-DiJ64oem.d.ts → faceCaptureManagerFactory-BxWPpxyd.d.ts} +63 -24
- package/dist/{faceCaptureSetup-BqhqfFHL.esm.js → faceCaptureSetup-C7Qv79fA.esm.js} +57 -36
- package/dist/faceMatchManager-BXp2WUT-.esm.js +201 -0
- package/dist/faceMatchManager-BnvrZM2F.d.ts +268 -0
- package/dist/{faceMatchStateMachine-MDb30gCm.esm.js → faceMatchStateMachine-CJiO2yLO.esm.js} +14 -10
- package/dist/feature-management-DS_Nj5Gg.esm.js +615 -0
- package/dist/feature-management.d.ts +173 -0
- package/dist/feature-management.esm.js +2 -0
- package/dist/field-comparison.d.ts +1 -4
- package/dist/field-comparison.esm.js +2 -7
- package/dist/{fieldComparisonManager-OLKVsG2Z.d.ts → fieldComparisonManager-CBvEGxFx.d.ts} +3 -5
- package/dist/{fieldComparisonManager-Cw4uzD7S.esm.js → fieldComparisonManager-DhnYdYZ2.esm.js} +6 -11
- package/dist/fiscal-qr.d.ts +1 -6
- package/dist/fiscal-qr.esm.js +14 -29
- package/dist/flow-events.d.ts +2 -9
- package/dist/flow.d.ts +59 -21
- package/dist/flow.esm.js +132 -49
- package/dist/{flowCompletionService-B6P6_EF3.esm.js → flowCompletionService-BSe3p7pg.esm.js} +4 -6
- package/dist/flowServices-unhF9V9F.esm.js +620 -0
- package/dist/geolocation.d.ts +1 -4
- package/dist/geolocation.esm.js +3 -9
- package/dist/{geolocationManager-CF6-NizA.esm.js → geolocationManager-Bv9MSVNr.esm.js} +44 -16
- package/dist/geolocationManager-Cz744E5S.d.ts +506 -0
- package/dist/{geolocationStateMachine-CD2XJs1b.esm.js → geolocationStateMachine-BxD6JK1M.esm.js} +53 -28
- package/dist/{getBrowser-1yNcfFZR.esm.js → getBrowser-Cb8Zgrgp.esm.js} +1 -2
- package/dist/getBrowser-DNrQeL4T.esm.js +16 -0
- package/dist/getDeviceClass-9y-kbVDw.esm.js +19 -0
- package/dist/{getDeviceClass-C-Dg6IsJ.esm.js → getDeviceClass-CeBiEayS.esm.js} +1 -2
- package/dist/getParsedUserAgent-pJjATANs.esm.js +1622 -0
- package/dist/government-validation.d.ts +3 -6
- package/dist/government-validation.esm.js +7 -13
- package/dist/{governmentValidationStateMachine-BE5wiQ7Y.esm.js → governmentValidationStateMachine-BYKYUdza.esm.js} +8 -11
- package/dist/home.d.ts +14 -22
- package/dist/home.esm.js +3 -8
- package/dist/http.d.ts +0 -1
- package/dist/http.esm.js +2 -4
- package/dist/id-ocr.d.ts +8 -7
- package/dist/id-ocr.esm.js +20 -9
- package/dist/id-selfie-flow.d.ts +179 -0
- package/dist/id-selfie-flow.esm.js +917 -0
- package/dist/id-verification.d.ts +2 -191
- package/dist/id-verification.esm.js +3 -43
- package/dist/id.d.ts +7 -18
- package/dist/id.esm.js +47 -53
- package/dist/{idCaptureDefaults-CZajItdO.esm.js → idCaptureDefaults-WeHbQM7W.esm.js} +54 -17
- package/dist/idCaptureManager-BjU-Wyow.d.ts +429 -0
- package/dist/{idCaptureManager-DASPUMtC.esm.js → idCaptureManager-BjiBSrHm.esm.js} +173 -34
- package/dist/idCaptureServices-CFDwqTtQ.esm.js +396 -0
- package/dist/{idCaptureStateMachine-DZ2ZTSxu.esm.js → idCaptureStateMachine-CRwQh8jc.esm.js} +438 -585
- package/dist/{idOcrManager-Tm4LzKR0.esm.js → idOcrManager-CvJZhmBR.esm.js} +4 -7
- package/dist/{idOcrManager-DLfAjDZn.d.ts → idOcrManager-DXc_xRN7.d.ts} +62 -229
- package/dist/{idOcrStateMachine-BJxZCxwS.esm.js → idOcrStateMachine-Bq_e3l4E.esm.js} +15 -20
- package/dist/idVerificationManager-BB4Pj_N6.d.ts +197 -0
- package/dist/idVerificationManager-CduNXhWd.esm.js +51 -0
- package/dist/{idVerificationStateMachine-DXN9WcdM.esm.js → idVerificationStateMachine-GEQWUPU2.esm.js} +5 -7
- package/dist/identity-reuse.d.ts +1 -4
- package/dist/identity-reuse.esm.js +3 -8
- package/dist/{identityReuseManager-LjkPvSQN.esm.js → identityReuseManager-D5vXknnJ.esm.js} +24 -7
- package/dist/{identityReuseManager-CLVOYxcp.d.ts → identityReuseManager-v2TH5g7e.d.ts} +38 -43
- package/dist/{identityReuseStateMachine-Bp-zONoV.esm.js → identityReuseStateMachine-AubRPhIi.esm.js} +6 -8
- package/dist/index-B8weJxmL.d.ts +1 -0
- package/dist/index-BKOWUhxG.d.ts +258 -0
- package/dist/{index-DrpWp15Z.d.ts → index-CLMF0QLQ.d.ts} +3 -3
- package/dist/index.d.ts +1 -3
- package/dist/index.esm.js +2 -15
- package/dist/{invokeOnCaptureCallback-CCS2Ruzi.esm.js → invokeOnCaptureCallback-1m1VJ3Q4.esm.js} +2 -4
- package/dist/{lib-Bjd6nOLG.esm.js → lib-DAyUtOTj.esm.js} +1510 -1589
- package/dist/mandatory-consent.d.ts +2 -10
- package/dist/mandatory-consent.esm.js +3 -8
- package/dist/{mandatoryConsentManager-DeBmOHcQ.d.ts → mandatoryConsentManager-B29kR0XA.d.ts} +60 -85
- package/dist/{mandatoryConsentManager-DZ26sg4B.esm.js → mandatoryConsentManager-BeELm1zS.esm.js} +4 -7
- package/dist/{mandatoryConsentStateMachine-_44ZoDSh.esm.js → mandatoryConsentStateMachine-zroQGzwb.esm.js} +5 -7
- package/dist/manualReviewService-D4IkipnN.esm.js +15 -0
- package/dist/{mediaRecorderOptions-CClCKAiF.esm.js → mediaRecorderOptions-dALSV8af.esm.js} +2 -4
- package/dist/{mediapipeShared-Bcmkzs3x.esm.js → mediapipeShared-B6oNoXEQ.esm.js} +21 -9
- package/dist/{onDeviceFaceResults-DILtthks.esm.js → onDeviceFaceResults-f81FVdQs.esm.js} +30 -87
- package/dist/openviduLazy-CCDpFWg1.esm.js +10 -0
- package/dist/{otp-B1YeyRXu.d.ts → otp-CEWfTj9v.d.ts} +0 -1
- package/dist/{otp-uaXiKt_R.esm.js → otp-D2k3PeSL.esm.js} +3 -14
- package/dist/{permissionGuards-BNOe4Tnu.esm.js → permissionGuards-KINpmKtI.esm.js} +2 -4
- package/dist/{permissionServices-C_kxhNeo.esm.js → permissionServices-UvUcOSRb.esm.js} +35 -28
- package/dist/personhood.d.ts +13 -20
- package/dist/personhood.esm.js +153 -85
- package/dist/phone.d.ts +1 -5
- package/dist/phone.esm.js +3 -10
- package/dist/{phoneManager-_KtitbUA.d.ts → phoneManager-BBU8Lq0O.d.ts} +4 -7
- package/dist/{phoneManager-CRblfdcI.esm.js → phoneManager-Cpiai2IM.esm.js} +82 -12
- package/dist/{phoneStateMachine-DjFGWMjQ.esm.js → phoneStateMachine-E1fUUcNM.esm.js} +7 -9
- package/dist/{platform-0Ot-bF_a.esm.js → platform-BJrm5nzZ.esm.js} +31 -11
- package/dist/privacyLensWorker-DI-Rg83h.esm.js +355 -0
- package/dist/qe-signature.d.ts +4 -6
- package/dist/qe-signature.esm.js +10 -11
- package/dist/{recordingService-CxaYkITO.esm.js → recordingService-CFpfdXSS.esm.js} +13 -31
- package/dist/redirect-to-mobile.d.ts +2 -6
- package/dist/redirect-to-mobile.esm.js +3 -10
- package/dist/{redirectToMobileManager-dv29o_LS.d.ts → redirectToMobileManager-BD2PvT5B.d.ts} +8 -44
- package/dist/{redirectToMobileManager-DbuNUlru.esm.js → redirectToMobileManager-C0dfc7f6.esm.js} +28 -8
- package/dist/{redirectToMobileStateMachine-BRaIJWOM.esm.js → redirectToMobileStateMachine-C9SYCJcH.esm.js} +74 -18
- package/dist/rolldown-runtime-FZ4Itg2g.esm.js +42 -0
- package/dist/{runChildModule-kojSv3MH.esm.js → runChildModule-DpvgAGFA.esm.js} +10 -9
- package/dist/{WasmUtilProvider-BgzcXBdl.esm.js → safeStorage-Ep56I-f_.esm.js} +98 -473
- package/dist/selfie.d.ts +8 -21
- package/dist/selfie.esm.js +40 -47
- package/dist/selfieErrorUtils-C2PYBVsp.esm.js +90 -0
- package/dist/{selfieManager-B1Px3GGa.esm.js → selfieManager-Br4onIMy.esm.js} +7 -10
- package/dist/{selfieManager-4juKfrHG.d.ts → selfieManager-DwlCppg0.d.ts} +3 -4
- package/dist/{selfieStateMachine-D0TS93Tu.esm.js → selfieStateMachine-CNGs8vFK.esm.js} +14 -29
- package/dist/{session-CLsU2Jnb.esm.js → session-CX2H3tmI.esm.js} +16 -7
- package/dist/session.d.ts +12 -10
- package/dist/session.esm.js +5 -15
- package/dist/sessionInitializer-CV6Rj_dn.esm.js +1865 -0
- package/dist/{setup-CfNsYAeB.esm.js → setup-CYdVacqv.esm.js} +319 -137
- package/dist/{setup-CmA2apmN.d.ts → setup-DywQeePQ.d.ts} +186 -4
- package/dist/signature.d.ts +2 -96
- package/dist/signature.esm.js +3 -108
- package/dist/signatureManager-DGBSWz2_.d.ts +106 -0
- package/dist/signatureManager-DdPgcmou.esm.js +113 -0
- package/dist/{signatureStateMachine-CdhlMih-.esm.js → signatureStateMachine-Cf73DGWO.esm.js} +5 -7
- package/dist/spawn-D9jgw9pW.d.ts +1441 -0
- package/dist/src-0Jurt29N.esm.js +4482 -0
- package/dist/{stats-BVy52wLy.esm.js → stats-B0nFKawG.esm.js} +3 -5
- package/dist/stats.esm.js +2 -5
- package/dist/topLevelStateName-B5i2avbh.esm.js +41 -0
- package/dist/{tri-CFAPtAUM.esm.js → tri-4RsGrIt6.esm.js} +3 -119
- package/dist/tri.d.ts +2 -2
- package/dist/tri.esm-BIjHhBJ2.esm.js +2620 -0
- package/dist/tri.esm.js +2 -4
- package/dist/trueSight-B8G85W4z.esm.js +1 -0
- package/dist/trueSightService-6dTpiaTh.esm.js +1894 -0
- package/dist/trust-graph.d.ts +4 -5
- package/dist/trust-graph.esm.js +13 -9
- package/dist/types-BEwVKkLJ.d.ts +15 -0
- package/dist/{types-BKLb3C42.esm.js → types-BN5RPPIZ.esm.js} +1 -2
- package/dist/types-BSBzhHRH.d.ts +57 -0
- package/dist/types-BX_XkMsQ2.d.ts +21 -0
- package/dist/types-BfHiko5D.d.ts +48 -0
- package/dist/{types-D8Cl1N2Z.d.ts → types-BfgmV7s72.d.ts} +2 -2
- package/dist/types-CJ7Yl9K-2.d.ts +164 -0
- package/dist/types-DPIDuhut.d.ts +40 -0
- package/dist/types-DVmXIdzW.d.ts +82 -0
- package/dist/types-DelMqZLe.d.ts +105 -0
- package/dist/types-EELWyWpd.d.ts +6 -0
- package/dist/{types-fO6KIdol.d.ts → types-Hmd7U0JA.d.ts} +80 -6
- package/dist/{types-BUgeB2X6.d.ts → types-KPvmag_B.d.ts} +7 -7
- package/dist/types-M6eb82ra2.d.ts +589 -0
- package/dist/{types-DV3wkhQ4.d.ts → types-Vb4P9ovf.d.ts} +1 -3
- package/dist/{urlUtils-CpiYwn7w.esm.js → urlUtils-D6phjq1-.esm.js} +1 -2
- package/dist/{video-ZY92rkel.esm.js → video-BtSWtHy1.esm.js} +1 -2
- package/dist/video-selfie.d.ts +71 -117
- package/dist/video-selfie.esm.js +575 -643
- package/dist/videoSelfieCameraWarmer-h_VUk5Vu.esm.js +651 -0
- package/dist/videoSelfieStepDetector-D3oo2ofo.d.ts +123 -0
- package/dist/wasm.d.ts +2 -4
- package/dist/wasm.esm.js +4 -15
- package/dist/wasmDefaults-DTUrftxR.esm.js +125 -0
- package/dist/{wasmDsFlagSync-Ch0mFJNT.esm.js → wasmDsFlagSync-BqUCwLY8.esm.js} +2 -4
- package/dist/wasmWebClient-DM8lkb0z.esm.js +407 -0
- package/dist/watchlist-for-business.d.ts +1 -3
- package/dist/watchlist-for-business.esm.js +9 -9
- package/dist/watchlist.d.ts +1 -52
- package/dist/watchlist.esm.js +3 -70
- package/dist/{watchlistForBusinessStateMachine-R1Yp1g5A.esm.js → watchlistForBusinessStateMachine-BcBMkMvV.esm.js} +5 -7
- package/dist/watchlistManager-CjSMmbyq.esm.js +83 -0
- package/dist/watchlistManager-DLXef90v.d.ts +68 -0
- package/dist/{watchlistStateMachine-D07UVq1m.esm.js → watchlistStateMachine-Bl7Wy9Cm.esm.js} +5 -7
- package/dist/workflow.d.ts +239 -203
- package/dist/workflow.esm.js +156 -101
- package/dist/xstate-actors.esm-CtI5S0f4.esm.js +310 -0
- package/dist/{xstate.esm-nY3M5UYm.esm.js → xstate.esm-Cf4e0Cfd.esm.js} +136 -610
- package/package.json +14 -9
- package/dist/Actor-BwgmOPkb.d.ts +0 -2
- package/dist/Face2DProvider-CZU11g6r.esm.js +0 -824
- package/dist/FaceDetectionProvider-CPWg-PAH.d.ts +0 -119
- package/dist/IWasmUtilCapability-DaSNxgA8.d.ts +0 -150
- package/dist/PrivacyLensProvider-B_zrV25p.esm.js +0 -339
- package/dist/StateMachine-D5WO6lHS.d.ts +0 -2
- package/dist/StreamCanvasCapture-DHaWferF.d.ts +0 -42
- package/dist/ae-signature-Pp4oCAHD.esm.js +0 -12
- package/dist/api-DsbUi7TO.esm.js +0 -72
- package/dist/browser-DlFgHpoC-Br3G10gw.esm.js +0 -1425
- package/dist/browserSimulation-DZwEy9bu.esm.js +0 -2804
- package/dist/camera-CxxXV0VN.esm.js +0 -79
- package/dist/canvas-90BqS0NF.d.ts +0 -112
- package/dist/chunk-CIKcdghV.esm.js +0 -49
- package/dist/clientLifecycle-VyzRfALg.esm.js +0 -534
- package/dist/cpf-ocr.d.ts +0 -10
- package/dist/cpf-ocr.esm.js +0 -8
- package/dist/cpfOcrManager-9eM4z4o7.esm.js +0 -190
- package/dist/cpfOcrManager-C5U9E0hf.d.ts +0 -216
- package/dist/createFaceAvatar-BZSg2MNo.esm.js +0 -46
- package/dist/deepsightLoader-CU_tiLMx.esm.js +0 -62
- package/dist/flowServices-DkQmOkLV.esm.js +0 -208
- package/dist/geolocationManager-DaHYFkVO.d.ts +0 -156
- package/dist/getDeviceClass-DIjnTPjR.esm.js +0 -14
- package/dist/http-CnsKEaJY.esm.js +0 -0
- package/dist/idCaptureManager-CwuXtVu6.d.ts +0 -1013
- package/dist/index-BAyZ8_iI.d.ts +0 -93
- package/dist/openviduLazy-jPgABwrt.esm.js +0 -3
- package/dist/openviduLazy-wEojM41w.esm.js +0 -12
- package/dist/qe-signature-BnWIpR-O.esm.js +0 -12
- package/dist/sessionInitializer-Dj7-aJaP.esm.js +0 -547
- package/dist/tri.esm-DDJ1zOqG.esm.js +0 -2593
- package/dist/types-BcF-fii5.d.ts +0 -5
- package/dist/videoSelfieSteps-Djpv7cjb.esm.js +0 -58
- /package/dist/{IVisibilityObserverCapability-BZ1rE7ly.d.ts → IVisibilityObserverCapability-D4tvIB5v.d.ts} +0 -0
- /package/dist/{camera-CKU96nwJ.d.ts → camera-9wWD-pwg.d.ts} +0 -0
- /package/dist/{flowCompletionService-BOSTlAWo.d.ts → flowCompletionService-BlAHsKyv.d.ts} +0 -0
- /package/dist/{types-BgjMAEXl.d.ts → types-Bk_6dmDa.d.ts} +0 -0
- /package/dist/{warmup-Dd0iq6KF.d.ts → warmup-Dv2alr2-.d.ts} +0 -0
package/dist/video-selfie.esm.js
CHANGED
|
@@ -1,422 +1,37 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as ensureWasmPipelines } from "./setup-
|
|
3
|
-
import { t as BrowserTimerProvider } from "./BrowserTimerProvider-
|
|
4
|
-
import { t as api } from "./api-
|
|
5
|
-
import "./
|
|
6
|
-
import "./
|
|
7
|
-
import { t as
|
|
8
|
-
import "./
|
|
9
|
-
import "./
|
|
10
|
-
import "./
|
|
11
|
-
import "./platform-0Ot-bF_a.esm.js";
|
|
12
|
-
import "./getBrowser-1yNcfFZR.esm.js";
|
|
13
|
-
import "./tri-CFAPtAUM.esm.js";
|
|
14
|
-
import { a as fromPromise, i as fromCallback, l as createManager, r as assign, s as createActor, t as setup } from "./xstate.esm-nY3M5UYm.esm.js";
|
|
15
|
-
import "./MotionSensorProvider-BHuHoYvJ.esm.js";
|
|
16
|
-
import { t as checkPermission } from "./permissionServices-C_kxhNeo.esm.js";
|
|
17
|
-
import { i as requestCameraWithFallback, r as requestCameraAccess } from "./camera-CxxXV0VN.esm.js";
|
|
18
|
-
import { a as encryptSelfieImage, b as FaceDetectionProvider, l as startDetection, m as uploadSelfie, p as processFace, y as StreamCanvasProcessingSession } from "./onDeviceFaceResults-DILtthks.esm.js";
|
|
19
|
-
import "./ITimerCapability-DJ73P4RK.esm.js";
|
|
20
|
-
import "./canvas-Pi10hG_l.esm.js";
|
|
21
|
-
import { n as getBackCameraStream } from "./backCameraStream-ChrMTJv-.esm.js";
|
|
22
|
-
import "./video-ZY92rkel.esm.js";
|
|
23
|
-
import { n as getWindowDimensions } from "./getDeviceClass-C-Dg6IsJ.esm.js";
|
|
24
|
-
import "./deepsightService-CViys6T4.esm.js";
|
|
25
|
-
import "./getDeviceClass-DIjnTPjR.esm.js";
|
|
26
|
-
import "./types-BKLb3C42.esm.js";
|
|
27
|
-
import { t as StreamCanvasCapture } from "./StreamCanvasCapture-CAqUdVkM.esm.js";
|
|
28
|
-
import { n as getSupportedMediaRecorderMimeType } from "./mediaRecorderOptions-CClCKAiF.esm.js";
|
|
29
|
-
import { n as resolveVideoSelfieSteps, r as videoSelfieWasmPipelines, t as facingModeForStep } from "./videoSelfieSteps-Djpv7cjb.esm.js";
|
|
30
|
-
import { i as IdCaptureProvider, n as DEFAULT_ID_CAPTURE_MODEL_VERSION, r as DEFAULT_ID_CAPTURE_THRESHOLDS, t as DEFAULT_ID_CAPTURE_GEOMETRY } from "./idCaptureDefaults-CZajItdO.esm.js";
|
|
31
|
-
|
|
32
|
-
//#region src/modules/video-selfie/videoSelfieCompareService.ts
|
|
33
|
-
async function postVideoSelfieCompare(endpoint, base64Image) {
|
|
34
|
-
return (await api.post(endpoint, { base64Image })).data;
|
|
35
|
-
}
|
|
36
|
-
/** Compare a front-ID frame from the video selfie against the captured ID. */
|
|
37
|
-
const compareVideoSelfieId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareId, base64Image);
|
|
38
|
-
/** Compare OCR from a front-ID frame against the captured ID's OCR. */
|
|
39
|
-
const compareVideoSelfieOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareOcr, base64Image);
|
|
40
|
-
/** Compare a back-ID frame from the video selfie against the captured ID. */
|
|
41
|
-
const compareVideoSelfieBackId = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackId, base64Image);
|
|
42
|
-
/** Compare OCR from a back-ID frame against the captured ID's OCR. */
|
|
43
|
-
const compareVideoSelfieBackOcr = (base64Image) => postVideoSelfieCompare(endpoints.videoSelfieCompareBackOcr, base64Image);
|
|
44
|
-
|
|
45
|
-
//#endregion
|
|
46
|
-
//#region src/modules/video-selfie/videoSelfieStepDetector.ts
|
|
47
|
-
/** Default autocapture window for the selfie step when the config omits one. */
|
|
48
|
-
const SELFIE_AUTOCAPTURE_MS = 1e4;
|
|
49
|
-
/**
|
|
50
|
-
* Whether the backend reported a confident face match on a front-ID compare.
|
|
51
|
-
* The backend normalizes `faceRecognitionConfidence` to 0 (no match) or 1 (match).
|
|
52
|
-
*/
|
|
53
|
-
function isFaceRecognitionMatch(confidence) {
|
|
54
|
-
if (typeof confidence !== "number") return true;
|
|
55
|
-
return confidence >= 1;
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Max selfie capture attempts before the step fails. Each backend rejection
|
|
59
|
-
* (e.g. 4077 low quality / liveness) re-arms the pipeline and scans again;
|
|
60
|
-
* after this many tries the step rejects. Mirrors V1's `numberOfTries`.
|
|
61
|
-
*/
|
|
62
|
-
const MAX_SELFIE_ATTEMPTS = 3;
|
|
63
|
-
/**
|
|
64
|
-
* How long the ID step holds on the "success" state (green check) after the
|
|
65
|
-
* backend validates a side, before advancing. Gives the user a beat to see the
|
|
66
|
-
* capture succeeded instead of the step jumping silently. Mirrors V1's
|
|
67
|
-
* IdVideoSelfie success flash.
|
|
68
|
-
*/
|
|
69
|
-
const ID_SUCCESS_DWELL_MS = 900;
|
|
70
|
-
/**
|
|
71
|
-
* How long an ID step holds the "show the front / back of your ID" instruction
|
|
72
|
-
* before autocapture is armed. No frame is captured or validated during this
|
|
73
|
-
* window, so the guidance can't be overwritten by capture feedback before the
|
|
74
|
-
* user has read it. Mirrors the UI's `ID_INSTRUCTION_DWELL_MS` message pin.
|
|
75
|
-
*/
|
|
76
|
-
const ID_INSTRUCTION_DWELL_MS = 3e3;
|
|
77
|
-
/** Resolves after `ms`, or rejects early if `signal` aborts. */
|
|
78
|
-
function delay(ms, signal) {
|
|
79
|
-
const timerProvider = BrowserTimerProvider.getInstance();
|
|
80
|
-
return new Promise((resolve, reject) => {
|
|
81
|
-
const timer = timerProvider.setTimeout(() => {
|
|
82
|
-
signal.removeEventListener("abort", onAbort);
|
|
83
|
-
resolve();
|
|
84
|
-
}, ms);
|
|
85
|
-
const onAbort = () => {
|
|
86
|
-
timerProvider.clearTimeout(timer);
|
|
87
|
-
reject(/* @__PURE__ */ new Error("aborted"));
|
|
88
|
-
};
|
|
89
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Builds the default per-step detector from the module config. Today it
|
|
94
|
-
* auto-captures the `selfie` step (face detection → `add/face` → `process/face`);
|
|
95
|
-
* ID / proof-of-address / questions / speech advance manually until their
|
|
96
|
-
* detectors land.
|
|
97
|
-
*/
|
|
98
|
-
function createDefaultStepDetector(config, deps) {
|
|
99
|
-
return ({ step, stream, signal, onStatus }) => {
|
|
100
|
-
if (step === "selfie") return detectSelfie({
|
|
101
|
-
config,
|
|
102
|
-
deps,
|
|
103
|
-
stream,
|
|
104
|
-
signal,
|
|
105
|
-
onStatus
|
|
106
|
-
});
|
|
107
|
-
if (step === "frontId" || step === "backId") return detectId({
|
|
108
|
-
step,
|
|
109
|
-
config,
|
|
110
|
-
stream,
|
|
111
|
-
signal,
|
|
112
|
-
onStatus
|
|
113
|
-
});
|
|
114
|
-
return new Promise(() => {});
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
async function detectSelfie(params) {
|
|
118
|
-
const { config, deps, stream, signal, onStatus } = params;
|
|
119
|
-
const provider = new FaceDetectionProvider();
|
|
120
|
-
await provider.initialize({
|
|
121
|
-
autocaptureInterval: SELFIE_AUTOCAPTURE_MS,
|
|
122
|
-
useOnDeviceWorkflow: false,
|
|
123
|
-
videoSelfie: true
|
|
124
|
-
});
|
|
125
|
-
provider.setChecksEnabled({
|
|
126
|
-
lenses: config.validateLenses ?? false,
|
|
127
|
-
mask: config.validateFaceMask ?? false,
|
|
128
|
-
closedEyes: config.validateClosedEyes ?? false,
|
|
129
|
-
headWear: config.validateHeadCover ?? false,
|
|
130
|
-
occlusion: false
|
|
131
|
-
});
|
|
132
|
-
const imageType = config.useAsSelfie === true ? "selfie" : "videoSelfie";
|
|
133
|
-
const capturer = new StreamCanvasCapture(stream);
|
|
134
|
-
const detectionConfig = {
|
|
135
|
-
showTutorial: false,
|
|
136
|
-
validateBrightness: false,
|
|
137
|
-
validateLenses: config.validateLenses ?? false,
|
|
138
|
-
validateFaceMask: config.validateFaceMask ?? false,
|
|
139
|
-
validateClosedEyes: config.validateClosedEyes ?? false,
|
|
140
|
-
validateHeadCover: config.validateHeadCover ?? false,
|
|
141
|
-
autoCaptureTimeout: SELFIE_AUTOCAPTURE_MS / 1e3
|
|
142
|
-
};
|
|
143
|
-
const runAttempt = () => new Promise((resolve, reject) => {
|
|
144
|
-
const onAbort = () => {
|
|
145
|
-
detection.cleanup();
|
|
146
|
-
reject(/* @__PURE__ */ new Error("aborted"));
|
|
147
|
-
};
|
|
148
|
-
const settle = (run) => {
|
|
149
|
-
signal.removeEventListener("abort", onAbort);
|
|
150
|
-
run();
|
|
151
|
-
};
|
|
152
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
153
|
-
const detection = startDetection({
|
|
154
|
-
config: detectionConfig,
|
|
155
|
-
capturer,
|
|
156
|
-
provider,
|
|
157
|
-
onUpdate: (status) => onStatus?.(status),
|
|
158
|
-
onSuccess: (canvas, faceCoordinates) => {
|
|
159
|
-
(async () => {
|
|
160
|
-
try {
|
|
161
|
-
await uploadSelfie({
|
|
162
|
-
encryptedBase64Image: await encryptSelfieImage({
|
|
163
|
-
canvas,
|
|
164
|
-
dependencies: { getWasmUtil: async () => deps.getWasmUtil() }
|
|
165
|
-
}),
|
|
166
|
-
faceCoordinates,
|
|
167
|
-
imageType
|
|
168
|
-
});
|
|
169
|
-
await processFace(imageType);
|
|
170
|
-
settle(resolve);
|
|
171
|
-
} catch (error$1) {
|
|
172
|
-
settle(() => reject(error$1 instanceof Error ? error$1 : new Error(String(error$1))));
|
|
173
|
-
}
|
|
174
|
-
})();
|
|
175
|
-
}
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
try {
|
|
179
|
-
for (let attempt = 1; attempt <= MAX_SELFIE_ATTEMPTS; attempt++) try {
|
|
180
|
-
await runAttempt();
|
|
181
|
-
return;
|
|
182
|
-
} catch (error$1) {
|
|
183
|
-
if (signal.aborted || attempt >= MAX_SELFIE_ATTEMPTS) throw error$1;
|
|
184
|
-
onStatus?.("error");
|
|
185
|
-
provider.reset();
|
|
186
|
-
await delay(1500, signal);
|
|
187
|
-
}
|
|
188
|
-
} finally {
|
|
189
|
-
capturer.dispose();
|
|
190
|
-
provider.dispose();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function detectId(params) {
|
|
194
|
-
const { step, config, stream, signal, onStatus } = params;
|
|
195
|
-
const provider = new IdCaptureProvider();
|
|
196
|
-
await provider.initialize({ videoSelfie: true });
|
|
197
|
-
const windowDimensions = getWindowDimensions();
|
|
198
|
-
provider.setGeometry({
|
|
199
|
-
...DEFAULT_ID_CAPTURE_GEOMETRY,
|
|
200
|
-
windowOuterWidth: windowDimensions.outerWidth,
|
|
201
|
-
windowOuterHeight: windowDimensions.outerHeight,
|
|
202
|
-
windowInnerWidth: windowDimensions.innerWidth,
|
|
203
|
-
windowInnerHeight: windowDimensions.innerHeight
|
|
204
|
-
});
|
|
205
|
-
provider.setSettings({
|
|
206
|
-
isFixedMask: false,
|
|
207
|
-
isIPhone14OrHigher: false,
|
|
208
|
-
idType: step === "backId" ? "BackId" : "FrontId",
|
|
209
|
-
blurCheckEnabled: true,
|
|
210
|
-
glareCheckEnabled: false,
|
|
211
|
-
faceQualityCheckEnabled: false,
|
|
212
|
-
iouCheckEnabled: true,
|
|
213
|
-
fpsLimitEnabled: false
|
|
214
|
-
});
|
|
215
|
-
provider.setThresholds(DEFAULT_ID_CAPTURE_THRESHOLDS);
|
|
216
|
-
provider.setModelType(DEFAULT_ID_CAPTURE_MODEL_VERSION);
|
|
217
|
-
const capturer = new StreamCanvasCapture(stream);
|
|
218
|
-
return new Promise((resolve, reject) => {
|
|
219
|
-
let validating = false;
|
|
220
|
-
let armed = false;
|
|
221
|
-
provider.setCallbacks({
|
|
222
|
-
onFarAway: () => {
|
|
223
|
-
if (armed && !validating) onStatus?.("tooFar");
|
|
224
|
-
},
|
|
225
|
-
onBlur: () => {
|
|
226
|
-
if (armed && !validating) onStatus?.("blur");
|
|
227
|
-
},
|
|
228
|
-
onCapturing: () => {
|
|
229
|
-
if (!armed || validating) return;
|
|
230
|
-
const base64Image = capturer.getLatestCanvas()?.getBase64Image();
|
|
231
|
-
if (!base64Image) return;
|
|
232
|
-
validating = true;
|
|
233
|
-
onStatus?.("capturing");
|
|
234
|
-
(async () => {
|
|
235
|
-
try {
|
|
236
|
-
if (await validateIdFrame(step, config, base64Image)) {
|
|
237
|
-
onStatus?.("success");
|
|
238
|
-
await delay(ID_SUCCESS_DWELL_MS, signal);
|
|
239
|
-
teardown();
|
|
240
|
-
resolve();
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
validating = false;
|
|
244
|
-
} catch (error$1) {
|
|
245
|
-
teardown();
|
|
246
|
-
reject(error$1 instanceof Error ? error$1 : new Error(String(error$1)));
|
|
247
|
-
}
|
|
248
|
-
})();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
const session = new StreamCanvasProcessingSession({
|
|
252
|
-
capturer,
|
|
253
|
-
provider
|
|
254
|
-
});
|
|
255
|
-
let toreDown = false;
|
|
256
|
-
const teardown = () => {
|
|
257
|
-
if (toreDown) return;
|
|
258
|
-
toreDown = true;
|
|
259
|
-
session.dispose();
|
|
260
|
-
capturer.dispose();
|
|
261
|
-
provider.dispose();
|
|
262
|
-
};
|
|
263
|
-
signal.addEventListener("abort", () => {
|
|
264
|
-
teardown();
|
|
265
|
-
reject(/* @__PURE__ */ new Error("aborted"));
|
|
266
|
-
}, { once: true });
|
|
267
|
-
delay(ID_INSTRUCTION_DWELL_MS, signal).then(() => {
|
|
268
|
-
if (toreDown || signal.aborted) return;
|
|
269
|
-
armed = true;
|
|
270
|
-
onStatus?.("detecting");
|
|
271
|
-
}).catch(() => {});
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
/** Runs the configured ID/OCR comparisons for a captured ID frame. */
|
|
275
|
-
async function validateIdFrame(step, config, base64Image) {
|
|
276
|
-
if (step === "backId") {
|
|
277
|
-
if (config.compareBackIdEnabled) {
|
|
278
|
-
const result = await compareVideoSelfieBackId(base64Image);
|
|
279
|
-
if (result.idSideMatched === false || result.success === false) return false;
|
|
280
|
-
}
|
|
281
|
-
if (config.compareBackOcrEnabled) {
|
|
282
|
-
if ((await compareVideoSelfieBackOcr(base64Image)).success === false) return false;
|
|
283
|
-
}
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
if (config.compareIdEnabled) {
|
|
287
|
-
const result = await compareVideoSelfieId(base64Image);
|
|
288
|
-
if (result.idTypeMatched === false) return false;
|
|
289
|
-
if (typeof result.faceRecognitionConfidence === "number" && !isFaceRecognitionMatch(result.faceRecognitionConfidence)) return false;
|
|
290
|
-
}
|
|
291
|
-
if (config.compareOcrEnabled) {
|
|
292
|
-
if ((await compareVideoSelfieOcr(base64Image)).success === false) return false;
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
//#endregion
|
|
298
|
-
//#region ../infra/src/media/CameraCompositor.ts
|
|
299
|
-
/**
|
|
300
|
-
* Composites the *active* camera into a canvas and exposes the canvas as a
|
|
301
|
-
* single, stable `MediaStream` to record. Switching cameras (front ↔ back)
|
|
302
|
-
* just repoints what the canvas draws — the output stream and its audio track
|
|
303
|
-
* are never replaced — so a single `MediaRecorder` produces one continuous
|
|
304
|
-
* video that follows the camera switches (V1 parity), instead of restarting
|
|
305
|
-
* per camera.
|
|
306
|
-
*
|
|
307
|
-
* The audio track from the initial stream is carried through unchanged across
|
|
308
|
-
* video switches, so there's no audio glitch when the camera flips.
|
|
309
|
-
*/
|
|
310
|
-
var CameraCompositor = class {
|
|
311
|
-
constructor(options = {}) {
|
|
312
|
-
this.canvas = null;
|
|
313
|
-
this.ctx = null;
|
|
314
|
-
this.video = null;
|
|
315
|
-
this.rafId = null;
|
|
316
|
-
this.audioTrack = null;
|
|
317
|
-
this.videoTrack = null;
|
|
318
|
-
this.composite = null;
|
|
319
|
-
this.fps = options.fps ?? 30;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Begin compositing `stream` and return the composite stream to record. The
|
|
323
|
-
* stream's audio track (if any) is carried through unchanged across later
|
|
324
|
-
* `switchVideo` calls.
|
|
325
|
-
*/
|
|
326
|
-
start(stream) {
|
|
327
|
-
this.canvas = document.createElement("canvas");
|
|
328
|
-
this.ctx = this.canvas.getContext("2d");
|
|
329
|
-
this.video = document.createElement("video");
|
|
330
|
-
this.video.muted = true;
|
|
331
|
-
this.video.playsInline = true;
|
|
332
|
-
this.audioTrack = stream.getAudioTracks()[0] ?? null;
|
|
333
|
-
this.setVideoSource(stream);
|
|
334
|
-
const composite = this.canvas.captureStream(this.fps);
|
|
335
|
-
if (this.audioTrack) composite.addTrack(this.audioTrack);
|
|
336
|
-
this.composite = composite;
|
|
337
|
-
this.startDrawLoop();
|
|
338
|
-
return composite;
|
|
339
|
-
}
|
|
340
|
-
/**
|
|
341
|
-
* Swap the camera feeding the canvas without interrupting the composite
|
|
342
|
-
* stream. Pass a video-only stream (audio is kept from `start`). The previous
|
|
343
|
-
* video track is stopped.
|
|
344
|
-
*/
|
|
345
|
-
switchVideo(stream) {
|
|
346
|
-
if (!this.composite) return;
|
|
347
|
-
const previousVideoTrack = this.videoTrack;
|
|
348
|
-
this.setVideoSource(stream);
|
|
349
|
-
if (previousVideoTrack) previousVideoTrack.stop();
|
|
350
|
-
}
|
|
351
|
-
/** The composite stream being recorded, if compositing is active. */
|
|
352
|
-
get stream() {
|
|
353
|
-
return this.composite ?? void 0;
|
|
354
|
-
}
|
|
355
|
-
/** Stop compositing and release the canvas, audio, and video tracks. */
|
|
356
|
-
stop() {
|
|
357
|
-
if (this.rafId !== null) {
|
|
358
|
-
cancelAnimationFrame(this.rafId);
|
|
359
|
-
this.rafId = null;
|
|
360
|
-
}
|
|
361
|
-
this.videoTrack?.stop();
|
|
362
|
-
this.audioTrack?.stop();
|
|
363
|
-
this.composite?.getTracks().forEach((track) => track.stop());
|
|
364
|
-
if (this.video) this.video.srcObject = null;
|
|
365
|
-
this.canvas = null;
|
|
366
|
-
this.ctx = null;
|
|
367
|
-
this.video = null;
|
|
368
|
-
this.videoTrack = null;
|
|
369
|
-
this.audioTrack = null;
|
|
370
|
-
this.composite = null;
|
|
371
|
-
}
|
|
372
|
-
setVideoSource(stream) {
|
|
373
|
-
this.videoTrack = stream.getVideoTracks()[0] ?? null;
|
|
374
|
-
const video = this.video;
|
|
375
|
-
if (!video) return;
|
|
376
|
-
video.srcObject = new MediaStream(stream.getVideoTracks());
|
|
377
|
-
video.play().catch(() => {});
|
|
378
|
-
}
|
|
379
|
-
startDrawLoop() {
|
|
380
|
-
const draw = () => {
|
|
381
|
-
const video = this.video;
|
|
382
|
-
const canvas = this.canvas;
|
|
383
|
-
const ctx = this.ctx;
|
|
384
|
-
if (video && canvas && ctx && video.videoWidth > 0) {
|
|
385
|
-
if (canvas.width !== video.videoWidth || canvas.height !== video.videoHeight) {
|
|
386
|
-
canvas.width = video.videoWidth;
|
|
387
|
-
canvas.height = video.videoHeight;
|
|
388
|
-
}
|
|
389
|
-
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
|
390
|
-
}
|
|
391
|
-
this.rafId = requestAnimationFrame(draw);
|
|
392
|
-
};
|
|
393
|
-
this.rafId = requestAnimationFrame(draw);
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
//#endregion
|
|
1
|
+
import { r as WasmUtilProvider } from "./safeStorage-Ep56I-f_.esm.js";
|
|
2
|
+
import { c as abortVideoSelfieUpload, d as uploadVideoSelfiePart, l as completeVideoSelfieUpload, t as ensureWasmPipelines, u as initiateVideoSelfieUpload } from "./setup-CYdVacqv.esm.js";
|
|
3
|
+
import { t as BrowserTimerProvider } from "./BrowserTimerProvider-BXhgNxLt.esm.js";
|
|
4
|
+
import { t as api } from "./api-Bx3gKW_y.esm.js";
|
|
5
|
+
import { t as endpoints } from "./endpoints-CCzL3XD_.esm.js";
|
|
6
|
+
import { d as videoSelfieWasmPipelines, l as facingModeForStep, u as resolveVideoSelfieSteps } from "./flowServices-unhF9V9F.esm.js";
|
|
7
|
+
import { c as createManager, o as createActor, r as assign, t as setup } from "./xstate.esm-Cf4e0Cfd.esm.js";
|
|
8
|
+
import { n as fromPromise, t as fromCallback } from "./xstate-actors.esm-CtI5S0f4.esm.js";
|
|
9
|
+
import { n as getSupportedMediaRecorderMimeType } from "./mediaRecorderOptions-dALSV8af.esm.js";
|
|
10
|
+
import { i as createDefaultStepDetector, n as defaultAcquireCamera, o as isConnectionError, r as CameraCompositor, t as createVideoSelfieCameraWarmer } from "./videoSelfieCameraWarmer-h_VUk5Vu.esm.js";
|
|
398
11
|
//#region ../infra/src/providers/browser/MultipartRecordingProvider.ts
|
|
399
12
|
/**
|
|
400
|
-
* S3 multipart requires every part except the last to be at least 5 MB, so we
|
|
401
|
-
* buffer recorded data until it crosses this threshold before emitting a part.
|
|
402
|
-
*/
|
|
403
|
-
const MIN_PART_SIZE_BYTES = 5 * 1024 * 1024;
|
|
404
|
-
/**
|
|
405
13
|
* How often `MediaRecorder` hands us buffered data (incremental blobs).
|
|
14
|
+
* 500 ms keeps each blob — and therefore each upload chunk — small: less
|
|
15
|
+
* tail lost when a session is interrupted, and a smaller first blob (init
|
|
16
|
+
* header + keyframe), which is the largest and most timeout-prone chunk.
|
|
17
|
+
* Not lower: request rate doubles per halving, and browsers stop honoring
|
|
18
|
+
* very small timeslices reliably.
|
|
406
19
|
*/
|
|
407
|
-
const TIMESLICE_MS =
|
|
20
|
+
const TIMESLICE_MS = 500;
|
|
408
21
|
/**
|
|
409
22
|
* Encoder target for the full video-selfie session. Higher than the
|
|
410
|
-
* short-clip liveness path (0.5–1 Mbps) so a multi-minute session keeps
|
|
411
|
-
* quality
|
|
412
|
-
* near-static face down to a single sub-5 MB part. It's a ceiling — a still
|
|
413
|
-
* scene still encodes well below it.
|
|
23
|
+
* short-clip liveness path (0.5–1 Mbps) so a multi-minute session keeps
|
|
24
|
+
* usable quality. It's a ceiling — a still scene still encodes well below it.
|
|
414
25
|
*/
|
|
415
26
|
const VIDEO_BITS_PER_SECOND = 25e5;
|
|
416
27
|
/**
|
|
417
|
-
* Records a stream and
|
|
418
|
-
*
|
|
419
|
-
*
|
|
28
|
+
* Records a stream and emits every `MediaRecorder` timeslice blob as its own
|
|
29
|
+
* part, in order. Each blob is a whole container fragment (WebM cluster /
|
|
30
|
+
* fMP4 `moof`+`mdat`), which is the property the video-selfie-service's
|
|
31
|
+
* gap-splicing depends on — parts must never cut a fragment at a byte
|
|
32
|
+
* offset. The service stages sub-5 MiB chunks and combines them server-side,
|
|
33
|
+
* so emitting the smallest whole-fragment unit (~0.5 s of video) maximizes
|
|
34
|
+
* what an interrupted session can salvage.
|
|
420
35
|
*
|
|
421
36
|
* Unlike the short-clip liveness recorder, this does NOT auto-pause on
|
|
422
37
|
* `visibilitychange`: a video-selfie session must record continuously, and a
|
|
@@ -430,8 +45,6 @@ var MultipartRecordingProvider = class {
|
|
|
430
45
|
this._hasError = false;
|
|
431
46
|
this._error = null;
|
|
432
47
|
this.mimeType = "";
|
|
433
|
-
this.buffer = [];
|
|
434
|
-
this.bufferedBytes = 0;
|
|
435
48
|
this.partNumber = 0;
|
|
436
49
|
this.onPart = null;
|
|
437
50
|
}
|
|
@@ -446,8 +59,6 @@ var MultipartRecordingProvider = class {
|
|
|
446
59
|
}
|
|
447
60
|
startRecording(stream, onPart) {
|
|
448
61
|
this.reset();
|
|
449
|
-
this.buffer = [];
|
|
450
|
-
this.bufferedBytes = 0;
|
|
451
62
|
this.partNumber = 0;
|
|
452
63
|
this.onPart = onPart;
|
|
453
64
|
try {
|
|
@@ -457,14 +68,17 @@ var MultipartRecordingProvider = class {
|
|
|
457
68
|
videoBitsPerSecond: VIDEO_BITS_PER_SECOND
|
|
458
69
|
});
|
|
459
70
|
recorder.ondataavailable = (event) => {
|
|
460
|
-
if (event.data.size > 0) {
|
|
461
|
-
this.
|
|
462
|
-
this.
|
|
463
|
-
|
|
71
|
+
if (event.data.size > 0 && this.onPart) {
|
|
72
|
+
this.partNumber += 1;
|
|
73
|
+
this.onPart({
|
|
74
|
+
chunk: event.data,
|
|
75
|
+
partNumber: this.partNumber
|
|
76
|
+
});
|
|
464
77
|
}
|
|
465
78
|
};
|
|
466
79
|
recorder.onerror = (event) => {
|
|
467
|
-
|
|
80
|
+
const detail = event instanceof ErrorEvent && event.error instanceof Error ? event.error.message : event.type;
|
|
81
|
+
this._error = `Recording error: ${detail}`;
|
|
468
82
|
this._isRecording = false;
|
|
469
83
|
this._hasError = true;
|
|
470
84
|
};
|
|
@@ -474,7 +88,8 @@ var MultipartRecordingProvider = class {
|
|
|
474
88
|
this._error = null;
|
|
475
89
|
this._hasError = false;
|
|
476
90
|
} catch (err) {
|
|
477
|
-
|
|
91
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
92
|
+
this._error = `Failed to start recording: ${detail}`;
|
|
478
93
|
this._hasError = true;
|
|
479
94
|
}
|
|
480
95
|
}
|
|
@@ -483,7 +98,6 @@ var MultipartRecordingProvider = class {
|
|
|
483
98
|
return new Promise((resolve) => {
|
|
484
99
|
if (recorder && this._isRecording) {
|
|
485
100
|
recorder.onstop = () => {
|
|
486
|
-
this.flushPart();
|
|
487
101
|
resolve();
|
|
488
102
|
};
|
|
489
103
|
recorder.stop();
|
|
@@ -503,202 +117,315 @@ var MultipartRecordingProvider = class {
|
|
|
503
117
|
this.mediaRecorder = null;
|
|
504
118
|
}
|
|
505
119
|
this.onPart = null;
|
|
506
|
-
this.buffer = [];
|
|
507
|
-
this.bufferedBytes = 0;
|
|
508
120
|
this._isRecording = false;
|
|
509
121
|
this._error = null;
|
|
510
122
|
this._hasError = false;
|
|
511
123
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
124
|
+
};
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region ../infra/src/providers/browser/BrowserNetworkStatusProvider.ts
|
|
127
|
+
var BrowserNetworkStatusProvider = class {
|
|
128
|
+
onOnline(listener) {
|
|
129
|
+
if (typeof window === "undefined" || typeof window.addEventListener !== "function") return () => {};
|
|
130
|
+
window.addEventListener("online", listener);
|
|
131
|
+
return () => {
|
|
132
|
+
window.removeEventListener("online", listener);
|
|
133
|
+
};
|
|
522
134
|
}
|
|
523
135
|
};
|
|
524
|
-
|
|
525
136
|
//#endregion
|
|
526
|
-
//#region src/internal/recordings/
|
|
137
|
+
//#region src/internal/recordings/videoSelfieChunkUploader.ts
|
|
527
138
|
/**
|
|
528
|
-
*
|
|
529
|
-
*
|
|
139
|
+
* How many part uploads run concurrently. The service allows 10 rps with a
|
|
140
|
+
* burst of 30 per session; a real recorder emits ~2 chunks/s, so this cap
|
|
141
|
+
* only matters when a backlog is draining after a network stall.
|
|
530
142
|
*/
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
143
|
+
const MAX_IN_FLIGHT = 3;
|
|
144
|
+
/** Attempts per chunk in the live round (first try + retries). */
|
|
145
|
+
const MAX_ATTEMPTS = 4;
|
|
146
|
+
/** Extra attempts per parked chunk during the final drain() round. */
|
|
147
|
+
const DRAIN_ATTEMPTS = 2;
|
|
148
|
+
const BACKOFF_BASE_MS = 500;
|
|
149
|
+
const BACKOFF_CAP_MS = 8e3;
|
|
150
|
+
/** Upper bound honored for a server-sent `Retry-After` (seconds). */
|
|
151
|
+
const RETRY_AFTER_CAP_MS = 3e4;
|
|
534
152
|
/**
|
|
535
|
-
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
153
|
+
* Ceiling on un-acknowledged chunk bytes held in memory. On a network slower
|
|
154
|
+
* than the encoder the backlog grows without bound; past this the upload
|
|
155
|
+
* cannot realistically catch up, so the uploader fails instead of eating
|
|
156
|
+
* memory on a low-end device. ~48 MiB ≈ 2.5 minutes of video at our bitrate.
|
|
538
157
|
*/
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
158
|
+
const MAX_BUFFERED_BYTES = 48 * 1024 * 1024;
|
|
159
|
+
function httpStatusOf$1(error) {
|
|
160
|
+
if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return error.status;
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
function retryAfterMsOf(error) {
|
|
164
|
+
if (typeof error === "object" && error !== null && "headers" in error && typeof error.headers === "object") {
|
|
165
|
+
const headers = error.headers;
|
|
166
|
+
const seconds = Number(headers?.["retry-after"]);
|
|
167
|
+
if (Number.isFinite(seconds) && seconds >= 0) return Math.min(seconds * 1e3, RETRY_AFTER_CAP_MS);
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
551
170
|
}
|
|
552
171
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
172
|
+
* Uploads video-selfie chunks with the client-side half of the service's
|
|
173
|
+
* resilience contract (`video-selfie-service` README, "What a client can
|
|
174
|
+
* rely on"):
|
|
175
|
+
*
|
|
176
|
+
* - **Keep-until-ack** — every chunk is held in memory until its upload
|
|
177
|
+
* returns 200 (a hole in the sequence can only be filled by that exact
|
|
178
|
+
* data). Acked bytes are durable in S3 (stored as a part or staged), so
|
|
179
|
+
* the blob is released the moment the ack lands.
|
|
180
|
+
* - **Retry with backoff** — transient failures (network error/timeout,
|
|
181
|
+
* 408, 429, 5xx) retry the same bytes under the same partNumber with
|
|
182
|
+
* exponential backoff + full jitter; 429 honors the server's
|
|
183
|
+
* `Retry-After`. Chunks may land out of order — the service stages them.
|
|
184
|
+
* - **Park, then drain** — a chunk that exhausts its live retries is parked
|
|
185
|
+
* (blob kept) without failing the recording; {@link drain} gives every
|
|
186
|
+
* parked chunk a final round once recording stops, so a mid-recording
|
|
187
|
+
* network outage only fails the upload if it persists to the end. A
|
|
188
|
+
* browser `online` event un-parks early: parked chunks get a fresh live
|
|
189
|
+
* round as soon as connectivity returns instead of waiting for stop.
|
|
190
|
+
* - **409 is fatal** — a newer initiate fenced this session out ("latest
|
|
191
|
+
* initiate wins"); no retry can succeed, so the uploader stops.
|
|
192
|
+
* - **Bounded memory** — when un-acked bytes exceed the ceiling the
|
|
193
|
+
* uploader fails rather than buffering a runaway backlog.
|
|
555
194
|
*/
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
195
|
+
var VideoSelfieChunkUploader = class {
|
|
196
|
+
constructor(session, dependencies = {}) {
|
|
197
|
+
this.queue = [];
|
|
198
|
+
this.parked = [];
|
|
199
|
+
this.inFlight = 0;
|
|
200
|
+
this.bufferedBytes = 0;
|
|
201
|
+
this.fatal = false;
|
|
202
|
+
this.disposed = false;
|
|
203
|
+
this.tasks = [];
|
|
204
|
+
this.session = session;
|
|
205
|
+
this.timer = dependencies.timer ?? BrowserTimerProvider.getInstance();
|
|
206
|
+
this.jitter = dependencies.jitter ?? Math.random;
|
|
207
|
+
const networkStatus = dependencies.networkStatus ?? new BrowserNetworkStatusProvider();
|
|
208
|
+
this.unsubscribeOnline = networkStatus.onOnline(() => this.unparkEarly());
|
|
209
|
+
}
|
|
210
|
+
/** True once the upload cannot complete (fenced out or over the memory cap). */
|
|
211
|
+
get failed() {
|
|
212
|
+
return this.fatal;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Accepts the next chunk for upload. Non-blocking: the upload (and any
|
|
216
|
+
* retries) run in the background; failures surface through {@link failed}
|
|
217
|
+
* and {@link drain}.
|
|
218
|
+
*/
|
|
219
|
+
enqueue(partNumber, chunk) {
|
|
220
|
+
if (this.fatal || this.disposed) return;
|
|
221
|
+
this.bufferedBytes += chunk.size;
|
|
222
|
+
if (this.bufferedBytes > MAX_BUFFERED_BYTES) {
|
|
223
|
+
this.fatal = true;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.queue.push({
|
|
227
|
+
partNumber,
|
|
228
|
+
chunk,
|
|
229
|
+
attempts: 0
|
|
230
|
+
});
|
|
231
|
+
this.pump();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Waits for every accepted chunk to be acknowledged, giving parked chunks
|
|
235
|
+
* one final retry round. Returns `true` when all chunks landed and the
|
|
236
|
+
* upload can be completed.
|
|
237
|
+
*/
|
|
238
|
+
async drain() {
|
|
239
|
+
this.unsubscribeOnline();
|
|
240
|
+
await this.settleLiveRound();
|
|
241
|
+
while (!this.fatal && this.parked.length > 0) {
|
|
242
|
+
const chunk = this.parked.shift();
|
|
243
|
+
chunk.attempts = MAX_ATTEMPTS - DRAIN_ATTEMPTS;
|
|
244
|
+
this.queue.push(chunk);
|
|
245
|
+
this.pump();
|
|
246
|
+
await this.settleLiveRound();
|
|
247
|
+
if (this.parked.some((p) => p.partNumber === chunk.partNumber)) return false;
|
|
248
|
+
}
|
|
249
|
+
return !this.fatal && this.parked.length === 0;
|
|
250
|
+
}
|
|
251
|
+
/** Cancels all pending work; held blobs are released. */
|
|
252
|
+
dispose() {
|
|
253
|
+
this.disposed = true;
|
|
254
|
+
this.queue = [];
|
|
255
|
+
this.parked = [];
|
|
256
|
+
this.unsubscribeOnline();
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Connectivity is back (browser `online` event) — give parked chunks a
|
|
260
|
+
* fresh live round right away instead of holding them until {@link drain}.
|
|
261
|
+
* The event is only a hint: chunks that still fail simply park again.
|
|
262
|
+
*/
|
|
263
|
+
unparkEarly() {
|
|
264
|
+
if (this.fatal || this.disposed || this.parked.length === 0) return;
|
|
265
|
+
for (const chunk of this.parked.splice(0)) {
|
|
266
|
+
chunk.attempts = 0;
|
|
267
|
+
this.queue.push(chunk);
|
|
268
|
+
}
|
|
269
|
+
this.pump();
|
|
270
|
+
}
|
|
271
|
+
async settleLiveRound() {
|
|
272
|
+
while (this.tasks.length > 0) {
|
|
273
|
+
const snapshot = [...this.tasks];
|
|
274
|
+
await Promise.all(snapshot);
|
|
275
|
+
this.tasks = this.tasks.filter((task) => !snapshot.includes(task));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
pump() {
|
|
279
|
+
while (!this.fatal && !this.disposed && this.inFlight < MAX_IN_FLIGHT && this.queue.length > 0) {
|
|
280
|
+
const chunk = this.queue.shift();
|
|
281
|
+
this.inFlight += 1;
|
|
282
|
+
const task = this.uploadWithRetry(chunk).finally(() => {
|
|
283
|
+
this.inFlight -= 1;
|
|
284
|
+
this.pump();
|
|
285
|
+
});
|
|
286
|
+
this.tasks.push(task);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
async uploadWithRetry(pending) {
|
|
290
|
+
while (!this.disposed) {
|
|
291
|
+
pending.attempts += 1;
|
|
292
|
+
try {
|
|
293
|
+
await uploadVideoSelfiePart(this.session, pending.partNumber, pending.chunk);
|
|
294
|
+
this.bufferedBytes -= pending.chunk.size;
|
|
295
|
+
return;
|
|
296
|
+
} catch (error) {
|
|
297
|
+
const status = httpStatusOf$1(error);
|
|
298
|
+
if (status === 409) {
|
|
299
|
+
this.fatal = true;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (status !== null && status >= 400 && status < 500 && status !== 408 && status !== 429 || pending.attempts >= MAX_ATTEMPTS) {
|
|
303
|
+
this.parked.push(pending);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
await this.delayBeforeRetry(pending.attempts, error);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
delayBeforeRetry(attempt, error) {
|
|
311
|
+
const retryAfterMs = retryAfterMsOf(error);
|
|
312
|
+
const backoffCeiling = Math.min(BACKOFF_BASE_MS * 2 ** (attempt - 1), BACKOFF_CAP_MS);
|
|
313
|
+
const waitMs = retryAfterMs ?? backoffCeiling / 2 + this.jitter() * (backoffCeiling / 2);
|
|
314
|
+
return new Promise((resolve) => {
|
|
315
|
+
this.timer.setTimeout(resolve, waitMs);
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
};
|
|
563
319
|
//#endregion
|
|
564
320
|
//#region src/internal/recordings/multipartRecordingService.ts
|
|
321
|
+
/** Attempts for `complete` — documented safe to repeat if the response was lost. */
|
|
322
|
+
const COMPLETE_ATTEMPTS = 3;
|
|
323
|
+
const COMPLETE_BACKOFF_MS = 500;
|
|
324
|
+
function httpStatusOf(error) {
|
|
325
|
+
if (typeof error === "object" && error !== null && "status" in error && typeof error.status === "number") return error.status;
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
565
328
|
/**
|
|
566
329
|
* Records the camera on-device and streams it to the backend as it is
|
|
567
|
-
* captured, using the
|
|
568
|
-
* (`/
|
|
330
|
+
* captured, using the video-selfie-service multipart upload protocol
|
|
331
|
+
* (`/v1/video-upload/*`).
|
|
569
332
|
*
|
|
570
333
|
* This is the Video Selfie module's client-side recorder and is deliberately
|
|
571
334
|
* independent of the face-capture recording services (`LocalRecordingService`
|
|
572
335
|
* / `OpenViduRecordingService`) — it is the replacement for server-side
|
|
573
336
|
* (OpenVidu) recording, not a sibling of it.
|
|
574
337
|
*
|
|
575
|
-
*
|
|
576
|
-
*
|
|
577
|
-
*
|
|
578
|
-
*
|
|
338
|
+
* Every ~1 s recorder blob uploads as its own chunk, concurrently with
|
|
339
|
+
* recording, through {@link VideoSelfieChunkUploader} (keep-until-ack +
|
|
340
|
+
* retry with backoff — see its doc for the full policy). The service stages
|
|
341
|
+
* sub-5 MiB chunks and combines them server-side, so `stop()` only drains
|
|
342
|
+
* the stragglers and finalizes; client memory holds un-acked chunks only.
|
|
343
|
+
* No client-side encryption: video is encrypted at rest server-side
|
|
579
344
|
* (S3 SSE-AES-256).
|
|
580
345
|
*/
|
|
581
346
|
var MultipartRecordingService = class {
|
|
582
347
|
constructor() {
|
|
583
348
|
this.recorder = new MultipartRecordingProvider();
|
|
584
|
-
this.
|
|
585
|
-
this.
|
|
586
|
-
this.
|
|
587
|
-
this.
|
|
349
|
+
this.timer = BrowserTimerProvider.getInstance();
|
|
350
|
+
this.session = null;
|
|
351
|
+
this.uploader = null;
|
|
352
|
+
this.chunksEmitted = 0;
|
|
588
353
|
}
|
|
589
354
|
get isRecording() {
|
|
590
355
|
return this.recorder.isRecording;
|
|
591
356
|
}
|
|
592
357
|
async start(stream) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
358
|
+
let initiated = await initiateVideoSelfieUpload();
|
|
359
|
+
if (initiated.parts.length > 0) {
|
|
360
|
+
await abortVideoSelfieUpload(initiated).catch(() => {});
|
|
361
|
+
initiated = await initiateVideoSelfieUpload();
|
|
362
|
+
}
|
|
363
|
+
this.session = {
|
|
364
|
+
uploadId: initiated.uploadId,
|
|
365
|
+
epoch: initiated.epoch
|
|
366
|
+
};
|
|
367
|
+
this.uploader = new VideoSelfieChunkUploader(this.session);
|
|
368
|
+
this.chunksEmitted = 0;
|
|
597
369
|
this.recorder.startRecording(stream, ({ chunk, partNumber }) => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
const task = uploadVideoSelfiePart(uploadId, partNumber, chunk).then((part) => {
|
|
601
|
-
this.parts.push(part);
|
|
602
|
-
}).catch(() => {
|
|
603
|
-
this.uploadFailed = true;
|
|
604
|
-
});
|
|
605
|
-
this.inflight.push(task);
|
|
370
|
+
this.chunksEmitted += 1;
|
|
371
|
+
this.uploader?.enqueue(partNumber, chunk);
|
|
606
372
|
});
|
|
607
373
|
}
|
|
608
374
|
async stop() {
|
|
609
|
-
const
|
|
610
|
-
|
|
375
|
+
const session = this.session;
|
|
376
|
+
const uploader = this.uploader;
|
|
377
|
+
if (!this.recorder.isRecording || !session || !uploader) return { recordingId: null };
|
|
611
378
|
await this.recorder.stopRecording();
|
|
612
|
-
await
|
|
613
|
-
|
|
614
|
-
|
|
379
|
+
if (!await uploader.drain() || uploader.failed || this.chunksEmitted === 0) {
|
|
380
|
+
this.abandonUpload(session);
|
|
381
|
+
return { recordingId: null };
|
|
382
|
+
}
|
|
615
383
|
try {
|
|
616
|
-
await
|
|
617
|
-
|
|
384
|
+
await this.completeWithRetry(session);
|
|
385
|
+
this.session = null;
|
|
386
|
+
this.uploader = null;
|
|
387
|
+
return { recordingId: session.uploadId };
|
|
618
388
|
} catch {
|
|
389
|
+
this.abandonUpload(session);
|
|
619
390
|
return { recordingId: null };
|
|
620
391
|
}
|
|
621
392
|
}
|
|
622
393
|
cleanup() {
|
|
394
|
+
if (this.session) this.abandonUpload(this.session);
|
|
623
395
|
this.recorder.reset();
|
|
624
396
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
*
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
if (facingMode !== "environment") return requestCameraWithFallback({
|
|
642
|
-
video: { facingMode },
|
|
643
|
-
audio: withAudio
|
|
644
|
-
});
|
|
645
|
-
const { stream } = await getBackCameraStream();
|
|
646
|
-
if (withAudio) try {
|
|
647
|
-
const audioStream = await requestCameraAccess({ audio: true });
|
|
648
|
-
for (const audioTrack of audioStream.getAudioTracks()) stream.addTrack(audioTrack);
|
|
649
|
-
} catch (error$1) {
|
|
650
|
-
stream.getTracks().forEach((track) => track.stop());
|
|
651
|
-
throw error$1;
|
|
652
|
-
}
|
|
653
|
-
return stream;
|
|
654
|
-
}
|
|
655
|
-
function stopStream(stream) {
|
|
656
|
-
stream?.getTracks().forEach((track) => track.stop());
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Creates a {@link VideoSelfieCameraWarmer} for the given config. The first
|
|
660
|
-
* camera (and whether it needs audio) is derived from the resolved step order —
|
|
661
|
-
* the same first acquire the recording machine performs on `START`.
|
|
662
|
-
*/
|
|
663
|
-
function createVideoSelfieCameraWarmer(config) {
|
|
664
|
-
const firstFacing = facingModeForStep(resolveVideoSelfieSteps(config)[0] ?? "selfie");
|
|
665
|
-
let warming = null;
|
|
666
|
-
let warmed = null;
|
|
667
|
-
let consumed = false;
|
|
668
|
-
return {
|
|
669
|
-
warm() {
|
|
670
|
-
if (warming) return;
|
|
671
|
-
warming = (async () => {
|
|
672
|
-
try {
|
|
673
|
-
if (await checkPermission() !== "granted") return null;
|
|
674
|
-
warmed = await defaultAcquireCamera(firstFacing, true);
|
|
675
|
-
return warmed;
|
|
676
|
-
} catch {
|
|
677
|
-
return null;
|
|
678
|
-
}
|
|
679
|
-
})();
|
|
680
|
-
},
|
|
681
|
-
async acquireCamera(facingMode, withAudio) {
|
|
682
|
-
if (!consumed && facingMode === firstFacing && withAudio) {
|
|
683
|
-
consumed = true;
|
|
684
|
-
const stream = warmed ?? (warming ? await warming : null);
|
|
685
|
-
warmed = null;
|
|
686
|
-
if (stream) return stream;
|
|
687
|
-
}
|
|
688
|
-
return defaultAcquireCamera(facingMode, withAudio);
|
|
689
|
-
},
|
|
690
|
-
dispose() {
|
|
691
|
-
if (consumed) return;
|
|
692
|
-
if (warmed) {
|
|
693
|
-
stopStream(warmed);
|
|
694
|
-
warmed = null;
|
|
695
|
-
} else if (warming) warming.then((stream) => {
|
|
696
|
-
if (!consumed) stopStream(stream);
|
|
397
|
+
/**
|
|
398
|
+
* `complete` is idempotent server-side ("safe to repeat if the response
|
|
399
|
+
* was lost"), so transient failures — network errors, 5xx, 429 — retry a
|
|
400
|
+
* couple of times before the recording is declared failed. A 4xx (e.g.
|
|
401
|
+
* `400` naming a missing part) is permanent: after a clean drain every
|
|
402
|
+
* chunk was acked, so a reported hole cannot be filled from here.
|
|
403
|
+
*/
|
|
404
|
+
async completeWithRetry(session) {
|
|
405
|
+
for (let attempt = 1;; attempt += 1) try {
|
|
406
|
+
await completeVideoSelfieUpload(session);
|
|
407
|
+
return;
|
|
408
|
+
} catch (error) {
|
|
409
|
+
const status = httpStatusOf(error);
|
|
410
|
+
if (status !== null && status >= 400 && status < 500 && status !== 408 && status !== 429 || attempt >= COMPLETE_ATTEMPTS) throw error;
|
|
411
|
+
await new Promise((resolve) => {
|
|
412
|
+
this.timer.setTimeout(resolve, COMPLETE_BACKOFF_MS * attempt);
|
|
697
413
|
});
|
|
698
414
|
}
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Fire-and-forget abort: the upload can no longer complete, so tell the
|
|
418
|
+
* backend to fail it immediately rather than at its expiry. Best-effort —
|
|
419
|
+
* an abort that doesn't land (including a 409 from a newer epoch having
|
|
420
|
+
* taken over) just means the server falls back to expiry or salvage.
|
|
421
|
+
*/
|
|
422
|
+
abandonUpload(session) {
|
|
423
|
+
this.session = null;
|
|
424
|
+
this.uploader?.dispose();
|
|
425
|
+
this.uploader = null;
|
|
426
|
+
abortVideoSelfieUpload(session).catch(() => {});
|
|
427
|
+
}
|
|
428
|
+
};
|
|
702
429
|
//#endregion
|
|
703
430
|
//#region src/modules/video-selfie/videoSelfieRecordingMachine.ts
|
|
704
431
|
/** A detector that never settles — used when a step has no auto-capture yet. */
|
|
@@ -718,6 +445,52 @@ function detectionStatusFromEvent(event) {
|
|
|
718
445
|
function detectionErrorFromEvent(event) {
|
|
719
446
|
return event.error;
|
|
720
447
|
}
|
|
448
|
+
function validationFailureFromEvent(event) {
|
|
449
|
+
return event.failure;
|
|
450
|
+
}
|
|
451
|
+
function errorKindFromEvent(event) {
|
|
452
|
+
return event.kind;
|
|
453
|
+
}
|
|
454
|
+
function retryableFailureFromEvent(event) {
|
|
455
|
+
return event.failure;
|
|
456
|
+
}
|
|
457
|
+
function orientationFromEvent(event) {
|
|
458
|
+
return event.orientation;
|
|
459
|
+
}
|
|
460
|
+
const SWITCHING_CAMERA_MIN_DWELL_MS = 3e3;
|
|
461
|
+
const ID_FLIP_DWELL_MS = 4e3;
|
|
462
|
+
const FINAL_SUCCESS_DWELL_MS = 3e3;
|
|
463
|
+
const VALIDATION_FEEDBACK_DWELL_MS = 3e3;
|
|
464
|
+
const DEFAULT_ATTEMPTS_PER_STEP = 3;
|
|
465
|
+
const UNLIMITED_ATTEMPTS = -1;
|
|
466
|
+
function normalizeAttempts(attempts) {
|
|
467
|
+
if (attempts === UNLIMITED_ATTEMPTS) return UNLIMITED_ATTEMPTS;
|
|
468
|
+
if (typeof attempts === "number" && attempts > 0) return attempts;
|
|
469
|
+
return DEFAULT_ATTEMPTS_PER_STEP;
|
|
470
|
+
}
|
|
471
|
+
function isIdStep(step) {
|
|
472
|
+
return step === "frontId" || step === "backId";
|
|
473
|
+
}
|
|
474
|
+
function nextModuleIndex(context) {
|
|
475
|
+
let index = context.stepIndex + 1;
|
|
476
|
+
while (isIdStep(context.steps[index])) index += 1;
|
|
477
|
+
return index;
|
|
478
|
+
}
|
|
479
|
+
function waitForDwell(ms, signal) {
|
|
480
|
+
const timerProvider = BrowserTimerProvider.getInstance();
|
|
481
|
+
return new Promise((resolve) => {
|
|
482
|
+
const onAbort = () => {
|
|
483
|
+
timerProvider.clearTimeout(timer);
|
|
484
|
+
resolve(false);
|
|
485
|
+
};
|
|
486
|
+
const timer = timerProvider.setTimeout(() => {
|
|
487
|
+
signal.removeEventListener("abort", onAbort);
|
|
488
|
+
resolve(true);
|
|
489
|
+
}, ms);
|
|
490
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
491
|
+
if (signal.aborted) onAbort();
|
|
492
|
+
});
|
|
493
|
+
}
|
|
721
494
|
/**
|
|
722
495
|
* Orchestrates a multi-step Video Selfie session as ONE continuous recording
|
|
723
496
|
* that follows the camera between steps AND auto-advances per step:
|
|
@@ -731,9 +504,9 @@ function detectionErrorFromEvent(event) {
|
|
|
731
504
|
* validation; success advances the step (switching the camera when the next
|
|
732
505
|
* step's facing differs), and the final step finalizes the multipart upload.
|
|
733
506
|
*
|
|
734
|
-
* Teardown: `ABORT` is accepted from ANY non-final state and every
|
|
735
|
-
*
|
|
736
|
-
*
|
|
507
|
+
* Teardown: `ABORT` is accepted from ANY non-final state and every settled
|
|
508
|
+
* path runs `cleanup`, so no path leaves camera or mic tracks running.
|
|
509
|
+
* XState v5 executes NO exit actions on an external
|
|
737
510
|
* `actor.stop()`, so the manager sends `ABORT` before stopping the actor —
|
|
738
511
|
* never stop this actor directly without aborting first.
|
|
739
512
|
*/
|
|
@@ -743,6 +516,11 @@ const videoSelfieRecordingSetup = setup({
|
|
|
743
516
|
events: {},
|
|
744
517
|
input: {}
|
|
745
518
|
},
|
|
519
|
+
delays: {
|
|
520
|
+
ID_FLIP_DWELL: ID_FLIP_DWELL_MS,
|
|
521
|
+
FINAL_SUCCESS_DWELL: FINAL_SUCCESS_DWELL_MS,
|
|
522
|
+
VALIDATION_FEEDBACK_DWELL: VALIDATION_FEEDBACK_DWELL_MS
|
|
523
|
+
},
|
|
746
524
|
actors: {
|
|
747
525
|
acquireCamera: fromPromise(async ({ input, signal }) => {
|
|
748
526
|
const stream = await input.acquire(input.facingMode, input.withAudio);
|
|
@@ -752,6 +530,23 @@ const videoSelfieRecordingSetup = setup({
|
|
|
752
530
|
}
|
|
753
531
|
return stream;
|
|
754
532
|
}),
|
|
533
|
+
switchCamera: fromPromise(async ({ input, signal }) => {
|
|
534
|
+
const dwellCompleted = waitForDwell(input.minDwellMs, signal);
|
|
535
|
+
const stream = await input.acquire(input.facingMode, false);
|
|
536
|
+
if (signal.aborted) {
|
|
537
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
538
|
+
throw new Error("Camera switch cancelled");
|
|
539
|
+
}
|
|
540
|
+
if (!await dwellCompleted) {
|
|
541
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
542
|
+
throw new Error("Camera switch cancelled");
|
|
543
|
+
}
|
|
544
|
+
if (signal.aborted) {
|
|
545
|
+
stream.getTracks().forEach((track) => track.stop());
|
|
546
|
+
throw new Error("Camera switch cancelled");
|
|
547
|
+
}
|
|
548
|
+
return stream;
|
|
549
|
+
}),
|
|
755
550
|
startRecording: fromPromise(async ({ input }) => {
|
|
756
551
|
await input.service.start(input.stream);
|
|
757
552
|
}),
|
|
@@ -765,35 +560,104 @@ const videoSelfieRecordingSetup = setup({
|
|
|
765
560
|
onStatus: (status) => sendBack({
|
|
766
561
|
type: "DETECTION_STATUS",
|
|
767
562
|
status
|
|
563
|
+
}),
|
|
564
|
+
onOrientation: (orientation) => sendBack({
|
|
565
|
+
type: "ORIENTATION",
|
|
566
|
+
orientation
|
|
768
567
|
})
|
|
769
|
-
}).then(() =>
|
|
568
|
+
}).then((result) => {
|
|
569
|
+
if (result.outcome === "retryableFailure") {
|
|
570
|
+
sendBack({
|
|
571
|
+
type: "RETRYABLE_FAILURE",
|
|
572
|
+
failure: result.failure
|
|
573
|
+
});
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
if (result.outcome === "validationFailed") {
|
|
577
|
+
sendBack({
|
|
578
|
+
type: "VALIDATION_FAILED",
|
|
579
|
+
failure: result.failure
|
|
580
|
+
});
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
sendBack({ type: "DETECTION_DONE" });
|
|
584
|
+
}).catch((err) => {
|
|
770
585
|
if (!controller.signal.aborted) sendBack({
|
|
771
586
|
type: "DETECTION_FAILED",
|
|
772
|
-
error: String(err)
|
|
587
|
+
error: String(err),
|
|
588
|
+
kind: isConnectionError(err) ? "connection" : "technical"
|
|
773
589
|
});
|
|
774
590
|
});
|
|
775
591
|
return () => controller.abort();
|
|
776
592
|
})
|
|
777
593
|
},
|
|
778
594
|
actions: {
|
|
779
|
-
advanceStep: assign({
|
|
595
|
+
advanceStep: assign({
|
|
596
|
+
stepIndex: ({ context }) => context.stepIndex + 1,
|
|
597
|
+
attemptsRemaining: ({ context }) => context.attemptsPerStep,
|
|
598
|
+
validationFailure: () => void 0
|
|
599
|
+
}),
|
|
600
|
+
advancePastIdSection: assign({
|
|
601
|
+
stepIndex: ({ context }) => nextModuleIndex(context),
|
|
602
|
+
attemptsRemaining: ({ context }) => context.attemptsPerStep,
|
|
603
|
+
validationFailure: () => void 0
|
|
604
|
+
}),
|
|
780
605
|
setDetectionStatus: assign({ detectionStatus: ({ event }) => detectionStatusFromEvent(event) }),
|
|
606
|
+
setOrientation: assign({ orientation: ({ event }) => orientationFromEvent(event) }),
|
|
781
607
|
setDetectionError: assign({ detectionError: ({ event }) => detectionErrorFromEvent(event) }),
|
|
782
|
-
|
|
608
|
+
setValidationFailure: assign(({ context, event }) => ({
|
|
609
|
+
attemptsRemaining: context.attemptsRemaining === UNLIMITED_ATTEMPTS ? UNLIMITED_ATTEMPTS : Math.max(0, context.attemptsRemaining - 1),
|
|
610
|
+
validationFailure: validationFailureFromEvent(event),
|
|
611
|
+
detectionStatus: "error"
|
|
612
|
+
})),
|
|
613
|
+
setUncountedRetryableFailure: assign(({ event }) => ({
|
|
614
|
+
validationFailure: retryableFailureFromEvent(event),
|
|
615
|
+
detectionStatus: "error"
|
|
616
|
+
})),
|
|
617
|
+
setErrorFromDetection: assign({
|
|
618
|
+
error: ({ event }) => detectionErrorFromEvent(event),
|
|
619
|
+
errorKind: ({ event }) => errorKindFromEvent(event)
|
|
620
|
+
}),
|
|
783
621
|
clearDetectionFeedback: assign({
|
|
784
622
|
detectionStatus: () => void 0,
|
|
785
|
-
detectionError: () => void 0
|
|
623
|
+
detectionError: () => void 0,
|
|
624
|
+
validationFailure: () => void 0,
|
|
625
|
+
orientation: () => void 0
|
|
626
|
+
}),
|
|
627
|
+
setUploadFailedError: assign({
|
|
628
|
+
error: () => "Video upload failed",
|
|
629
|
+
errorKind: () => "connection"
|
|
786
630
|
}),
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
631
|
+
resetSession: assign(({ context }) => ({
|
|
632
|
+
stepIndex: 0,
|
|
633
|
+
stream: void 0,
|
|
634
|
+
composite: void 0,
|
|
635
|
+
detectionStatus: void 0,
|
|
636
|
+
orientation: void 0,
|
|
637
|
+
detectionError: void 0,
|
|
638
|
+
validationFailure: void 0,
|
|
639
|
+
attemptsRemaining: context.attemptsPerStep,
|
|
640
|
+
recordingId: null,
|
|
641
|
+
error: void 0,
|
|
642
|
+
errorKind: void 0
|
|
643
|
+
})),
|
|
644
|
+
cleanup: ({ context }) => {
|
|
645
|
+
context.service.cleanup();
|
|
646
|
+
context.compositor.stop();
|
|
647
|
+
context.stream?.getTracks().forEach((track) => track.stop());
|
|
792
648
|
}
|
|
793
649
|
},
|
|
794
650
|
guards: {
|
|
795
|
-
isLastStep: ({ context
|
|
796
|
-
|
|
651
|
+
isLastStep: ({ context }) => context.stepIndex >= context.steps.length - 1,
|
|
652
|
+
hasAttemptsRemaining: ({ context }) => context.attemptsRemaining === UNLIMITED_ATTEMPTS || context.attemptsRemaining > 0,
|
|
653
|
+
nextStepFlipsId: ({ context }) => context.steps[context.stepIndex] === "frontId" && context.steps[context.stepIndex + 1] === "backId",
|
|
654
|
+
exhaustedIdFinishes: ({ context }) => isIdStep(context.steps[context.stepIndex]) && nextModuleIndex(context) >= context.steps.length,
|
|
655
|
+
nextModuleFlipsCamera: ({ context }) => isIdStep(context.steps[context.stepIndex]) && facingModeForStep(context.steps[context.stepIndex]) !== facingModeForStep(context.steps[nextModuleIndex(context)]),
|
|
656
|
+
isIdExhaustion: ({ context }) => isIdStep(context.steps[context.stepIndex]),
|
|
657
|
+
exhaustedNonIdFinishes: ({ context }) => !isIdStep(context.steps[context.stepIndex]) && context.stepIndex >= context.steps.length - 1,
|
|
658
|
+
exhaustedNonIdNextStepFlipsCamera: ({ context }) => !isIdStep(context.steps[context.stepIndex]) && facingModeForStep(context.steps[context.stepIndex]) !== facingModeForStep(context.steps[context.stepIndex + 1]),
|
|
659
|
+
isNonIdExhaustion: ({ context }) => !isIdStep(context.steps[context.stepIndex]),
|
|
660
|
+
nextStepFlipsCamera: ({ context }) => facingModeForStep(context.steps[context.stepIndex]) !== facingModeForStep(context.steps[context.stepIndex + 1]),
|
|
797
661
|
uploadReturnedNoRecordingId: ({ event }) => recordingIdFromEvent(event) === null
|
|
798
662
|
}
|
|
799
663
|
});
|
|
@@ -802,6 +666,10 @@ const ADVANCE_TRANSITIONS = [
|
|
|
802
666
|
target: "finalizing",
|
|
803
667
|
guard: "isLastStep"
|
|
804
668
|
},
|
|
669
|
+
{
|
|
670
|
+
target: "flippingId",
|
|
671
|
+
guard: "nextStepFlipsId"
|
|
672
|
+
},
|
|
805
673
|
{
|
|
806
674
|
target: "switchingCamera",
|
|
807
675
|
guard: "nextStepFlipsCamera",
|
|
@@ -813,7 +681,7 @@ const ADVANCE_TRANSITIONS = [
|
|
|
813
681
|
actions: "advanceStep"
|
|
814
682
|
}
|
|
815
683
|
];
|
|
816
|
-
const
|
|
684
|
+
const videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
817
685
|
id: "videoSelfieRecording",
|
|
818
686
|
initial: "idle",
|
|
819
687
|
context: ({ input }) => ({
|
|
@@ -826,27 +694,33 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
826
694
|
stream: void 0,
|
|
827
695
|
composite: void 0,
|
|
828
696
|
detectionStatus: void 0,
|
|
697
|
+
orientation: void 0,
|
|
829
698
|
detectionError: void 0,
|
|
699
|
+
validationFailure: void 0,
|
|
700
|
+
attemptsPerStep: normalizeAttempts(input.attemptsPerStep),
|
|
701
|
+
attemptsRemaining: normalizeAttempts(input.attemptsPerStep),
|
|
830
702
|
recordingId: null,
|
|
831
|
-
error: void 0
|
|
703
|
+
error: void 0,
|
|
704
|
+
errorKind: void 0,
|
|
705
|
+
switchingCameraMinDwellMs: input.dependencies?.switchingCameraMinDwellMs ?? SWITCHING_CAMERA_MIN_DWELL_MS
|
|
832
706
|
}),
|
|
833
707
|
on: { ABORT: { target: ".aborted" } },
|
|
834
708
|
states: {
|
|
835
709
|
idle: { on: { START: { target: "acquiringCamera" } } },
|
|
836
710
|
acquiringCamera: { invoke: {
|
|
837
711
|
src: "acquireCamera",
|
|
838
|
-
input: ({ context
|
|
839
|
-
acquire: context
|
|
840
|
-
facingMode: facingModeForStep(context
|
|
712
|
+
input: ({ context }) => ({
|
|
713
|
+
acquire: context.acquireCamera,
|
|
714
|
+
facingMode: facingModeForStep(context.steps[context.stepIndex]),
|
|
841
715
|
withAudio: true
|
|
842
716
|
}),
|
|
843
717
|
onDone: {
|
|
844
718
|
target: "starting",
|
|
845
|
-
actions: [({ context
|
|
846
|
-
context
|
|
719
|
+
actions: [({ context, event }) => {
|
|
720
|
+
context.compositor.start(eventOutputStream(event));
|
|
847
721
|
}, assign({
|
|
848
722
|
stream: ({ event }) => eventOutputStream(event),
|
|
849
|
-
composite: ({ context
|
|
723
|
+
composite: ({ context }) => context.compositor.stream
|
|
850
724
|
})]
|
|
851
725
|
},
|
|
852
726
|
onError: {
|
|
@@ -856,9 +730,9 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
856
730
|
} },
|
|
857
731
|
starting: { invoke: {
|
|
858
732
|
src: "startRecording",
|
|
859
|
-
input: ({ context
|
|
860
|
-
service: context
|
|
861
|
-
stream: context
|
|
733
|
+
input: ({ context }) => ({
|
|
734
|
+
service: context.service,
|
|
735
|
+
stream: context.composite
|
|
862
736
|
}),
|
|
863
737
|
onDone: { target: "recording" },
|
|
864
738
|
onError: {
|
|
@@ -870,33 +744,87 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
870
744
|
entry: "clearDetectionFeedback",
|
|
871
745
|
invoke: {
|
|
872
746
|
src: "detectStep",
|
|
873
|
-
input: ({ context
|
|
874
|
-
detect: context
|
|
875
|
-
step: context
|
|
876
|
-
stream: context
|
|
747
|
+
input: ({ context }) => ({
|
|
748
|
+
detect: context.detect,
|
|
749
|
+
step: context.steps[context.stepIndex],
|
|
750
|
+
stream: context.stream
|
|
877
751
|
})
|
|
878
752
|
},
|
|
879
753
|
on: {
|
|
880
754
|
DETECTION_STATUS: { actions: "setDetectionStatus" },
|
|
755
|
+
ORIENTATION: { actions: "setOrientation" },
|
|
881
756
|
DETECTION_DONE: ADVANCE_TRANSITIONS,
|
|
882
757
|
NEXT_STEP: ADVANCE_TRANSITIONS,
|
|
758
|
+
VALIDATION_FAILED: {
|
|
759
|
+
target: "validationFeedback",
|
|
760
|
+
actions: "setValidationFailure"
|
|
761
|
+
},
|
|
762
|
+
RETRYABLE_FAILURE: {
|
|
763
|
+
target: "validationFeedback",
|
|
764
|
+
actions: "setUncountedRetryableFailure"
|
|
765
|
+
},
|
|
883
766
|
DETECTION_FAILED: {
|
|
884
767
|
target: "error",
|
|
885
768
|
actions: "setErrorFromDetection"
|
|
886
769
|
}
|
|
887
770
|
}
|
|
888
771
|
},
|
|
772
|
+
validationFeedback: { after: { VALIDATION_FEEDBACK_DWELL: [{
|
|
773
|
+
target: "recording",
|
|
774
|
+
guard: "hasAttemptsRemaining"
|
|
775
|
+
}, ...[
|
|
776
|
+
{
|
|
777
|
+
target: "finalizing",
|
|
778
|
+
guard: "exhaustedIdFinishes"
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
target: "switchingCamera",
|
|
782
|
+
guard: "nextModuleFlipsCamera",
|
|
783
|
+
actions: "advancePastIdSection"
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
target: "recording",
|
|
787
|
+
reenter: true,
|
|
788
|
+
guard: "isIdExhaustion",
|
|
789
|
+
actions: "advancePastIdSection"
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
target: "finalizing",
|
|
793
|
+
guard: "exhaustedNonIdFinishes"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
target: "switchingCamera",
|
|
797
|
+
guard: "exhaustedNonIdNextStepFlipsCamera",
|
|
798
|
+
actions: "advanceStep"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
target: "recording",
|
|
802
|
+
reenter: true,
|
|
803
|
+
guard: "isNonIdExhaustion",
|
|
804
|
+
actions: "advanceStep"
|
|
805
|
+
}
|
|
806
|
+
]] } },
|
|
807
|
+
flippingId: {
|
|
808
|
+
after: { ID_FLIP_DWELL: {
|
|
809
|
+
target: "recording",
|
|
810
|
+
actions: "advanceStep"
|
|
811
|
+
} },
|
|
812
|
+
on: { NEXT_STEP: {
|
|
813
|
+
target: "recording",
|
|
814
|
+
actions: "advanceStep"
|
|
815
|
+
} }
|
|
816
|
+
},
|
|
889
817
|
switchingCamera: { invoke: {
|
|
890
|
-
src: "
|
|
891
|
-
input: ({ context
|
|
892
|
-
acquire: context
|
|
893
|
-
facingMode: facingModeForStep(context
|
|
894
|
-
|
|
818
|
+
src: "switchCamera",
|
|
819
|
+
input: ({ context }) => ({
|
|
820
|
+
acquire: context.acquireCamera,
|
|
821
|
+
facingMode: facingModeForStep(context.steps[context.stepIndex]),
|
|
822
|
+
minDwellMs: context.switchingCameraMinDwellMs
|
|
895
823
|
}),
|
|
896
824
|
onDone: {
|
|
897
825
|
target: "recording",
|
|
898
|
-
actions: [({ context
|
|
899
|
-
context
|
|
826
|
+
actions: [({ context, event }) => {
|
|
827
|
+
context.compositor.switchVideo(eventOutputStream(event));
|
|
900
828
|
}, assign({ stream: ({ event }) => eventOutputStream(event) })]
|
|
901
829
|
},
|
|
902
830
|
onError: {
|
|
@@ -906,13 +834,13 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
906
834
|
} },
|
|
907
835
|
finalizing: { invoke: {
|
|
908
836
|
src: "finalizeRecording",
|
|
909
|
-
input: ({ context
|
|
837
|
+
input: ({ context }) => ({ service: context.service }),
|
|
910
838
|
onDone: [{
|
|
911
839
|
target: "error",
|
|
912
840
|
guard: "uploadReturnedNoRecordingId",
|
|
913
841
|
actions: [assign({ recordingId: ({ event }) => recordingIdFromEvent(event) }), "setUploadFailedError"]
|
|
914
842
|
}, {
|
|
915
|
-
target: "
|
|
843
|
+
target: "success",
|
|
916
844
|
actions: assign({ recordingId: ({ event }) => recordingIdFromEvent(event) })
|
|
917
845
|
}],
|
|
918
846
|
onError: {
|
|
@@ -920,13 +848,17 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
920
848
|
actions: assign({ error: ({ event }) => errorFromEvent(event) })
|
|
921
849
|
}
|
|
922
850
|
} },
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
851
|
+
success: {
|
|
852
|
+
entry: "cleanup",
|
|
853
|
+
after: { FINAL_SUCCESS_DWELL: "finished" }
|
|
926
854
|
},
|
|
855
|
+
finished: { type: "final" },
|
|
927
856
|
error: {
|
|
928
|
-
|
|
929
|
-
|
|
857
|
+
entry: "cleanup",
|
|
858
|
+
on: { RETRY: {
|
|
859
|
+
target: "acquiringCamera",
|
|
860
|
+
actions: "resetSession"
|
|
861
|
+
} }
|
|
930
862
|
},
|
|
931
863
|
aborted: {
|
|
932
864
|
type: "final",
|
|
@@ -934,8 +866,6 @@ const _videoSelfieRecordingMachine = videoSelfieRecordingSetup.createMachine({
|
|
|
934
866
|
}
|
|
935
867
|
}
|
|
936
868
|
});
|
|
937
|
-
const videoSelfieRecordingMachine = _videoSelfieRecordingMachine;
|
|
938
|
-
|
|
939
869
|
//#endregion
|
|
940
870
|
//#region src/modules/video-selfie/videoSelfieRecordingActor.ts
|
|
941
871
|
function createVideoSelfieRecordingActor(options = {}) {
|
|
@@ -943,41 +873,54 @@ function createVideoSelfieRecordingActor(options = {}) {
|
|
|
943
873
|
const detect = options.dependencies?.detect ?? (config ? createDefaultStepDetector(config, { getWasmUtil: () => WasmUtilProvider.getInstance() }) : void 0);
|
|
944
874
|
return createActor(videoSelfieRecordingMachine, { input: {
|
|
945
875
|
steps: config ? resolveVideoSelfieSteps(config) : void 0,
|
|
876
|
+
attemptsPerStep: config?.numberOfTries,
|
|
946
877
|
dependencies: {
|
|
947
878
|
...options.dependencies,
|
|
948
879
|
detect
|
|
949
880
|
}
|
|
950
881
|
} }).start();
|
|
951
882
|
}
|
|
952
|
-
|
|
953
883
|
//#endregion
|
|
954
884
|
//#region src/modules/video-selfie/videoSelfieRecordingManager.ts
|
|
955
885
|
function mapState(snapshot) {
|
|
956
886
|
const value = typeof snapshot.value === "string" ? snapshot.value : "idle";
|
|
957
|
-
const { context
|
|
887
|
+
const { context } = snapshot;
|
|
958
888
|
const status = value === "acquiringCamera" ? "starting" : value;
|
|
959
|
-
const steps = context
|
|
960
|
-
const stepIndex = context
|
|
889
|
+
const steps = context.steps;
|
|
890
|
+
const stepIndex = context.stepIndex;
|
|
961
891
|
return {
|
|
962
892
|
status,
|
|
963
893
|
step: steps[stepIndex],
|
|
964
894
|
stepIndex,
|
|
965
895
|
isLastStep: stepIndex >= steps.length - 1,
|
|
966
|
-
stream: context
|
|
967
|
-
composite: context
|
|
968
|
-
detectionStatus: context
|
|
969
|
-
|
|
970
|
-
|
|
896
|
+
stream: context.stream,
|
|
897
|
+
composite: context.composite,
|
|
898
|
+
detectionStatus: context.detectionStatus,
|
|
899
|
+
orientation: context.orientation,
|
|
900
|
+
validationFailure: context.validationFailure,
|
|
901
|
+
attemptsRemaining: context.attemptsRemaining,
|
|
902
|
+
recordingId: context.recordingId,
|
|
903
|
+
error: context.error,
|
|
904
|
+
errorKind: context.errorKind
|
|
971
905
|
};
|
|
972
906
|
}
|
|
973
907
|
function createApi({ actor }) {
|
|
974
908
|
return {
|
|
909
|
+
/** Acquire the first camera, open the upload, and begin recording. */
|
|
975
910
|
start() {
|
|
976
911
|
actor.send({ type: "START" });
|
|
977
912
|
},
|
|
913
|
+
retry() {
|
|
914
|
+
actor.send({ type: "RETRY" });
|
|
915
|
+
},
|
|
916
|
+
/**
|
|
917
|
+
* Advance to the next step. Switches the camera when the next step's
|
|
918
|
+
* facing mode differs; finalizes the upload on the last step.
|
|
919
|
+
*/
|
|
978
920
|
nextStep() {
|
|
979
921
|
actor.send({ type: "NEXT_STEP" });
|
|
980
922
|
},
|
|
923
|
+
/** Abandon the session without finalizing the upload. */
|
|
981
924
|
abort() {
|
|
982
925
|
actor.send({ type: "ABORT" });
|
|
983
926
|
}
|
|
@@ -1006,7 +949,6 @@ function createVideoSelfieRecordingManager(options = {}) {
|
|
|
1006
949
|
}
|
|
1007
950
|
};
|
|
1008
951
|
}
|
|
1009
|
-
|
|
1010
952
|
//#endregion
|
|
1011
953
|
//#region src/modules/video-selfie/videoSelfieWasm.ts
|
|
1012
954
|
/**
|
|
@@ -1022,7 +964,6 @@ function createVideoSelfieRecordingManager(options = {}) {
|
|
|
1022
964
|
function warmupVideoSelfieWasm(config) {
|
|
1023
965
|
return ensureWasmPipelines(videoSelfieWasmPipelines(config));
|
|
1024
966
|
}
|
|
1025
|
-
|
|
1026
967
|
//#endregion
|
|
1027
968
|
//#region src/modules/video-selfie/videoSelfieSpeechService.ts
|
|
1028
969
|
/**
|
|
@@ -1062,7 +1003,6 @@ async function addSpeech(base64Mp3) {
|
|
|
1062
1003
|
return { success: false };
|
|
1063
1004
|
}
|
|
1064
1005
|
}
|
|
1065
|
-
|
|
1066
1006
|
//#endregion
|
|
1067
1007
|
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/wave-encoder/index.js
|
|
1068
1008
|
var wave_encoder_default = () => {
|
|
@@ -1109,7 +1049,6 @@ var wave_encoder_default = () => {
|
|
|
1109
1049
|
else if (e.data[0] === "dump") dump(e.data[1]);
|
|
1110
1050
|
};
|
|
1111
1051
|
};
|
|
1112
|
-
|
|
1113
1052
|
//#endregion
|
|
1114
1053
|
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/index.js
|
|
1115
1054
|
let AudioContext = window.AudioContext || window.webkitAudioContext;
|
|
@@ -1333,8 +1272,6 @@ MediaRecorder$1.notSupported = !navigator.mediaDevices || !AudioContext;
|
|
|
1333
1272
|
* MediaRecorder.encoder = oggEncoder
|
|
1334
1273
|
*/
|
|
1335
1274
|
MediaRecorder$1.encoder = wave_encoder_default;
|
|
1336
|
-
var audio_recorder_polyfill_default = MediaRecorder$1;
|
|
1337
|
-
|
|
1338
1275
|
//#endregion
|
|
1339
1276
|
//#region ../../node_modules/.pnpm/audio-recorder-polyfill@0.4.1/node_modules/audio-recorder-polyfill/mpeg-encoder/index.js
|
|
1340
1277
|
var mpeg_encoder_default = () => {
|
|
@@ -1343,7 +1280,7 @@ var mpeg_encoder_default = () => {
|
|
|
1343
1280
|
let KBPS = 128;
|
|
1344
1281
|
let DEFAULT_SAMPLE_RATE = 44100;
|
|
1345
1282
|
let encoder;
|
|
1346
|
-
let recorded = new Int8Array();
|
|
1283
|
+
let recorded = /* @__PURE__ */ new Int8Array();
|
|
1347
1284
|
function concat(a, b) {
|
|
1348
1285
|
if (b.length === 0) return a;
|
|
1349
1286
|
let c = new Int8Array(a.length + b.length);
|
|
@@ -1363,7 +1300,7 @@ var mpeg_encoder_default = () => {
|
|
|
1363
1300
|
let buf = encoder.flush();
|
|
1364
1301
|
recorded = concat(recorded, buf);
|
|
1365
1302
|
let buffer = recorded.buffer;
|
|
1366
|
-
recorded = new Int8Array();
|
|
1303
|
+
recorded = /* @__PURE__ */ new Int8Array();
|
|
1367
1304
|
postMessage(buffer, [buffer]);
|
|
1368
1305
|
}
|
|
1369
1306
|
onmessage = (e) => {
|
|
@@ -1372,7 +1309,6 @@ var mpeg_encoder_default = () => {
|
|
|
1372
1309
|
else dump(e.data[1]);
|
|
1373
1310
|
};
|
|
1374
1311
|
};
|
|
1375
|
-
|
|
1376
1312
|
//#endregion
|
|
1377
1313
|
//#region ../infra/src/media/blobEncoding.ts
|
|
1378
1314
|
/** Encodes a blob as raw base64 bytes (no `data:...;base64,` prefix). */
|
|
@@ -1394,10 +1330,9 @@ function blobToRawBase64(blob) {
|
|
|
1394
1330
|
reader.readAsDataURL(blob);
|
|
1395
1331
|
});
|
|
1396
1332
|
}
|
|
1397
|
-
|
|
1398
1333
|
//#endregion
|
|
1399
1334
|
//#region ../infra/src/providers/browser/VoiceRecordingProvider.ts
|
|
1400
|
-
const AudioRecorderPolyfill =
|
|
1335
|
+
const AudioRecorderPolyfill = MediaRecorder$1;
|
|
1401
1336
|
AudioRecorderPolyfill.encoder = mpeg_encoder_default;
|
|
1402
1337
|
AudioRecorderPolyfill.prototype.mimeType = "audio/mpeg";
|
|
1403
1338
|
const MIME_TYPE = "audio/mpeg";
|
|
@@ -1422,7 +1357,8 @@ var VoiceRecordingProvider = class {
|
|
|
1422
1357
|
start(stream) {
|
|
1423
1358
|
this.reset();
|
|
1424
1359
|
this.chunks = [];
|
|
1425
|
-
const
|
|
1360
|
+
const audioOnly = new MediaStream(stream.getAudioTracks());
|
|
1361
|
+
const recorder = new AudioRecorderPolyfill(audioOnly.clone());
|
|
1426
1362
|
recorder.mimeType = MIME_TYPE;
|
|
1427
1363
|
recorder.addEventListener("dataavailable", (event) => {
|
|
1428
1364
|
if (event.data.size > 0) this.chunks.push(event.data);
|
|
@@ -1464,7 +1400,6 @@ var VoiceRecordingProvider = class {
|
|
|
1464
1400
|
this._isRecording = false;
|
|
1465
1401
|
}
|
|
1466
1402
|
};
|
|
1467
|
-
|
|
1468
1403
|
//#endregion
|
|
1469
1404
|
//#region src/modules/video-selfie/videoSelfieConsentRecorder.ts
|
|
1470
1405
|
function createConsentRecorder() {
|
|
@@ -1483,7 +1418,6 @@ function createConsentRecorder() {
|
|
|
1483
1418
|
}
|
|
1484
1419
|
};
|
|
1485
1420
|
}
|
|
1486
|
-
|
|
1487
1421
|
//#endregion
|
|
1488
1422
|
//#region src/modules/video-selfie/videoSelfieFlowModuleMachine.ts
|
|
1489
1423
|
/**
|
|
@@ -1496,10 +1430,9 @@ function createConsentRecorder() {
|
|
|
1496
1430
|
* it starts in a non-final `running` state and never transitions, so the
|
|
1497
1431
|
* orchestrated flow stays in `runningModule` until the UI advances it.
|
|
1498
1432
|
*
|
|
1499
|
-
* **Completion contract:** the L2 `incode-video-selfie` web component
|
|
1500
|
-
* `flowManager.completeModule()` once the recording manager reaches `finished
|
|
1501
|
-
*
|
|
1502
|
-
* orchestrator remains in `runningModule` — this is intentional.
|
|
1433
|
+
* **Completion contract:** the L2 `incode-video-selfie` web component calls
|
|
1434
|
+
* `flowManager.completeModule()` once the recording manager reaches `finished`.
|
|
1435
|
+
* Recoverable errors stay inside the module until the user retries.
|
|
1503
1436
|
*
|
|
1504
1437
|
* **Abort / unmount:** if the user closes the module or the web component
|
|
1505
1438
|
* unmounts before completion, the orchestrated flow manager's existing close /
|
|
@@ -1515,6 +1448,5 @@ const videoSelfieFlowModuleMachine = setup({}).createMachine({
|
|
|
1515
1448
|
initial: "running",
|
|
1516
1449
|
states: { running: {} }
|
|
1517
1450
|
});
|
|
1518
|
-
|
|
1519
1451
|
//#endregion
|
|
1520
|
-
export { createConsentRecorder, createVideoSelfieCameraWarmer, createVideoSelfieRecordingManager, facingModeForStep, getVideoSelfieQuestions, getVoiceConsentPhrase, resolveVideoSelfieSteps, videoSelfieFlowModuleMachine, videoSelfieWasmPipelines, warmupVideoSelfieWasm };
|
|
1452
|
+
export { createConsentRecorder, createVideoSelfieCameraWarmer, createVideoSelfieRecordingManager, facingModeForStep, getVideoSelfieQuestions, getVoiceConsentPhrase, resolveVideoSelfieSteps, videoSelfieFlowModuleMachine, videoSelfieWasmPipelines, warmupVideoSelfieWasm };
|