facecog-liveness-showcase 0.0.1
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/.browserslistrc +15 -0
- package/.dockerignore +48 -0
- package/.editorconfig +16 -0
- package/.eslintrc.json +47 -0
- package/.vercelignore +7 -0
- package/.vscode/extensions.json +5 -0
- package/.vscode/settings.json +3 -0
- package/DOCKER.md +221 -0
- package/Dockerfile +33 -0
- package/README.md +268 -0
- package/angular.json +156 -0
- package/capacitor.config.ts +9 -0
- package/docker-compose.dev.yml +20 -0
- package/docker-compose.yml +18 -0
- package/ionic.config.json +7 -0
- package/jest.config.js +38 -0
- package/nginx.conf +50 -0
- package/package.json +131 -0
- package/patches/ng-packagr+20.3.2.patch +60 -0
- package/projects/facecog-liveness-verification/README.md +295 -0
- package/projects/facecog-liveness-verification/ng-package.json +7 -0
- package/projects/facecog-liveness-verification/package.json +48 -0
- package/projects/facecog-liveness-verification/scripts/build-with-wrapper-copy.js +38 -0
- package/projects/facecog-liveness-verification/scripts/copy-wrapper-after-ngc.js +35 -0
- package/projects/facecog-liveness-verification/sources/FaceLivenessReactWrapper.tsx +320 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.d.ts +28 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js +247 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.generated.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.js.map +1 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/FaceLivenessReactWrapper.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/components/aws-face-liveness/aws-face-liveness.component.ts +500 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.html +41 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.scss +234 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.spec.ts +158 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-permission/camera-permission.component.ts +58 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.html +34 -0
- package/projects/facecog-liveness-verification/src/lib/components/camera-verification/camera-verification.component.ts +210 -0
- package/projects/facecog-liveness-verification/src/lib/components/dialogs/save-custom-pose-dialog.component.ts +174 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.html +45 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.scss +87 -0
- package/projects/facecog-liveness-verification/src/lib/components/facetec-scan/facetec-scan.component.ts +182 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.html +394 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.scss +1567 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.spec.ts +699 -0
- package/projects/facecog-liveness-verification/src/lib/components/intro/intro.component.ts +721 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.html +120 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.scss +611 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.spec.ts +605 -0
- package/projects/facecog-liveness-verification/src/lib/components/live-preview/live-preview.component.ts +524 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.html +73 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.scss +19 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.spec.ts +673 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-flow/liveness-flow.component.ts +963 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.html +38 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.scss +10 -0
- package/projects/facecog-liveness-verification/src/lib/components/liveness-verification/liveness-verification.component.ts +233 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.spec.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/components/pose-selection/pose-selection.component.ts +33 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.html +17 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.scss +156 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.spec.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/components/processing/processing.component.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.html +190 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.scss +534 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.spec.ts +286 -0
- package/projects/facecog-liveness-verification/src/lib/components/verification-result/verification-result.component.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/analyze-response.interface.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/aws-face-liveness.interface.ts +46 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-adapter.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-http-client.interface.ts +93 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/backend-response.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/camera-provider.interface.ts +107 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/category-info.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-data.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-repository.interface.ts +48 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/custom-pose-response.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/index.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-action-result.interface.ts +13 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-config.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-metadata.interface.ts +17 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-result.interface.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/liveness-verification-config.interface.ts +41 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-analyze-response.interface.ts +21 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/multi-backend-liveness-result.interface.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-definition.interface.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-keypoint.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-match-result.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/pose-verify-response.interface.ts +8 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/scan-results.interface.ts +29 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-plan.interface.ts +42 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-progress-event.interface.ts +12 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-session.interface.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/verification-step-change-event.interface.ts +11 -0
- package/projects/facecog-liveness-verification/src/lib/interfaces/video-recording.interface.ts +9 -0
- package/projects/facecog-liveness-verification/src/lib/liveness-verification.module.ts +123 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/aws-face-liveness-component.token.ts +23 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/category-info.constant.ts +14 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/default-liveness-config.constant.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/liveness-verification-config.token.ts +16 -0
- package/projects/facecog-liveness-verification/src/lib/models/constants/pose-definitions.constant.ts +377 -0
- package/projects/facecog-liveness-verification/src/lib/models/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/index.ts +2 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.spec.ts +76 -0
- package/projects/facecog-liveness-verification/src/lib/models/utils/pose.utils.ts +59 -0
- package/projects/facecog-liveness-verification/src/lib/services/aws-face-liveness.service.ts +49 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.spec.ts +111 -0
- package/projects/facecog-liveness-verification/src/lib/services/backend-http.service.ts +130 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/azure-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.spec.ts +24 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/facetec-backend.service.ts +35 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.spec.ts +36 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/mock-backend.service.ts +39 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.spec.ts +81 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/openpose-backend.service.ts +72 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.spec.ts +69 -0
- package/projects/facecog-liveness-verification/src/lib/services/backends/rekognition-analysis-backend.service.ts +83 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.spec.ts +200 -0
- package/projects/facecog-liveness-verification/src/lib/services/camera.service.ts +155 -0
- package/projects/facecog-liveness-verification/src/lib/services/custom-poses-api.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/services/index.ts +18 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.spec.ts +103 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-backend.service.ts +61 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.spec.ts +109 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-config.service.ts +70 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.spec.ts +144 -0
- package/projects/facecog-liveness-verification/src/lib/services/liveness-orchestrator.service.ts +162 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/hand-gesture-detection.service.ts +315 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/index.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/openpose.service.ts +287 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-comparison.service.ts +353 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/pose-matching.service.ts +2370 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-detection/reference-pose.service.ts +271 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.spec.ts +183 -0
- package/projects/facecog-liveness-verification/src/lib/services/pose-selection.service.ts +179 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.spec.ts +159 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-api.service.ts +151 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.spec.ts +184 -0
- package/projects/facecog-liveness-verification/src/lib/services/verification-plan.service.ts +94 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.spec.ts +52 -0
- package/projects/facecog-liveness-verification/src/lib/services/video-recorder.service.ts +117 -0
- package/projects/facecog-liveness-verification/src/lib/types/detection-strategy.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/index.ts +7 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-action.type.ts +31 -0
- package/projects/facecog-liveness-verification/src/lib/types/liveness-backend.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-category.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/pose-difficulty.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-flow-step.type.ts +5 -0
- package/projects/facecog-liveness-verification/src/lib/types/verification-step-kind.type.ts +4 -0
- package/projects/facecog-liveness-verification/src/public-api.ts +150 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.json +20 -0
- package/projects/facecog-liveness-verification/tsconfig.lib.prod.json +11 -0
- package/projects/facecog-liveness-verification/tsconfig.spec.json +13 -0
- package/projects/facecog-liveness-verification/tsconfig.wrapper.json +15 -0
- package/projects/facecog-liveness-verification-test/src/app/app-routing.module.ts +22 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.html +3 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.scss +0 -0
- package/projects/facecog-liveness-verification-test/src/app/app.component.ts +11 -0
- package/projects/facecog-liveness-verification-test/src/app/app.module.ts +27 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home-routing.module.ts +16 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.module.ts +19 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.html +39 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.scss +97 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.spec.ts +24 -0
- package/projects/facecog-liveness-verification-test/src/app/home/home.page.ts +92 -0
- package/projects/facecog-liveness-verification-test/src/app/home/verification-modal.component.ts +106 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Bold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Medium_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Regular_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-SemiBold_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/fonts/gilroy/Gilroy-Thin_0.ttf +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/icon/favicon.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Five_Fingers_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Left_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Ok_Sign_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Peace_Sign_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/README.md +77 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Right_Palm.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Speak_Phrase.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Three_Fingers_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Thumbs_Up_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/Wave_Right.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/blink_twice.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/center_face.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/clap.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cover_right_eye.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/cross_arms.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/face_straight.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/follow_dot.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_down.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/look_up.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/move_closer.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/nod.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/open_mouth.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/raise_eyebrow.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/rotate_face.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/shake_head.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/smile.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/tilt_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_chin_left.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_left_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_nose_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/touch_right_cheek.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_left.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/turn_right.png +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/poses/wink.jpeg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/images/reference-pose.jpg +0 -0
- package/projects/facecog-liveness-verification-test/src/assets/shapes.svg +1 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.prod.ts +4 -0
- package/projects/facecog-liveness-verification-test/src/environments/environment.ts +17 -0
- package/projects/facecog-liveness-verification-test/src/global.scss +288 -0
- package/projects/facecog-liveness-verification-test/src/index.html +31 -0
- package/projects/facecog-liveness-verification-test/src/main.ts +6 -0
- package/projects/facecog-liveness-verification-test/src/polyfills.ts +55 -0
- package/projects/facecog-liveness-verification-test/src/theme/nextsapien-theme.scss +174 -0
- package/projects/facecog-liveness-verification-test/src/theme/variables.scss +2 -0
- package/projects/facecog-liveness-verification-test/src/zone-flags.ts +6 -0
- package/projects/facecog-liveness-verification-test/tsconfig.app.json +15 -0
- package/projects/facecog-liveness-verification-test/tsconfig.spec.json +14 -0
- package/setup-jest.ts +118 -0
- package/tsconfig.json +41 -0
- package/tsconfig.spec.json +15 -0
- package/vercel.json +24 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
OnInit,
|
|
4
|
+
OnDestroy,
|
|
5
|
+
AfterViewChecked,
|
|
6
|
+
Optional,
|
|
7
|
+
Inject,
|
|
8
|
+
ComponentRef,
|
|
9
|
+
NgZone,
|
|
10
|
+
Type,
|
|
11
|
+
ViewChild,
|
|
12
|
+
ViewContainerRef,
|
|
13
|
+
Output,
|
|
14
|
+
EventEmitter,
|
|
15
|
+
signal,
|
|
16
|
+
ChangeDetectionStrategy,
|
|
17
|
+
ChangeDetectorRef,
|
|
18
|
+
ApplicationRef
|
|
19
|
+
} from '@angular/core';
|
|
20
|
+
import { CommonModule } from '@angular/common';
|
|
21
|
+
import { IonicModule } from '@ionic/angular';
|
|
22
|
+
import { Observable, Subject, Subscription, firstValueFrom } from 'rxjs';
|
|
23
|
+
import { LivenessConfigService } from '../../services/liveness-config.service';
|
|
24
|
+
import { LivenessOrchestratorService } from '../../services/liveness-orchestrator.service';
|
|
25
|
+
import { PoseSelectionService } from '../../services/pose-selection.service';
|
|
26
|
+
import { VerificationApiService } from '../../services/verification-api.service';
|
|
27
|
+
import { IntroComponent } from '../intro/intro.component';
|
|
28
|
+
import { CameraPermissionComponent } from '../camera-permission/camera-permission.component';
|
|
29
|
+
import { LivePreviewComponent } from '../live-preview/live-preview.component';
|
|
30
|
+
import { FaceTecScanComponent } from '../facetec-scan/facetec-scan.component';
|
|
31
|
+
import { ProcessingComponent } from '../processing/processing.component';
|
|
32
|
+
import { VerificationResultComponent } from '../verification-result/verification-result.component';
|
|
33
|
+
import { AwsFaceLivenessComponent } from '../aws-face-liveness/aws-face-liveness.component';
|
|
34
|
+
import { AWS_FACE_LIVENESS_COMPONENT } from '../../models/constants/aws-face-liveness-component.token';
|
|
35
|
+
import type { ReferencePose } from '../../services/pose-detection/reference-pose.service';
|
|
36
|
+
import type { SelectedPoseInfo } from '../../services/pose-selection.service';
|
|
37
|
+
import type { LivenessConfig } from '../../interfaces/liveness-config.interface';
|
|
38
|
+
import type { LivenessResult } from '../../interfaces/liveness-result.interface';
|
|
39
|
+
import type { LivenessBackend } from '../../types/liveness-backend.type';
|
|
40
|
+
import type { FaceTecScanResult, AwsFaceLivenessScanResult } from '../../interfaces/scan-results.interface';
|
|
41
|
+
|
|
42
|
+
type FlowStep = 'intro' | 'permission' | 'preview' | 'facetec' | 'aws-liveness' | 'processing' | 'result';
|
|
43
|
+
|
|
44
|
+
const FLOW_VERBOSE = true;
|
|
45
|
+
|
|
46
|
+
function flowLog(tag: string, message: string, data?: unknown): void {
|
|
47
|
+
if (!FLOW_VERBOSE) return;
|
|
48
|
+
if (data !== undefined) {
|
|
49
|
+
console.log(`%c[LivenessFlow:${tag}] ${message}`, 'color: #00bcd4; font-weight: bold', data);
|
|
50
|
+
} else {
|
|
51
|
+
console.log(`%c[LivenessFlow:${tag}] ${message}`, 'color: #00bcd4; font-weight: bold');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function flowWarn(tag: string, message: string, data?: unknown): void {
|
|
56
|
+
console.warn(`[LivenessFlow:${tag}] ${message}`, data ?? '');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function flowError(tag: string, message: string, data?: unknown): void {
|
|
60
|
+
console.error(`[LivenessFlow:${tag}] ${message}`, data ?? '');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Component({
|
|
64
|
+
selector: 'facecog-liveness-flow',
|
|
65
|
+
standalone: true,
|
|
66
|
+
imports: [
|
|
67
|
+
CommonModule,
|
|
68
|
+
IonicModule,
|
|
69
|
+
IntroComponent,
|
|
70
|
+
CameraPermissionComponent,
|
|
71
|
+
LivePreviewComponent,
|
|
72
|
+
FaceTecScanComponent,
|
|
73
|
+
ProcessingComponent,
|
|
74
|
+
VerificationResultComponent,
|
|
75
|
+
AwsFaceLivenessComponent
|
|
76
|
+
],
|
|
77
|
+
templateUrl: './liveness-flow.component.html',
|
|
78
|
+
styleUrls: ['./liveness-flow.component.scss'],
|
|
79
|
+
changeDetection: ChangeDetectionStrategy.OnPush
|
|
80
|
+
})
|
|
81
|
+
export class LivenessFlowComponent implements OnInit, OnDestroy, AfterViewChecked {
|
|
82
|
+
@ViewChild('awsLivenessContainer', { read: ViewContainerRef }) awsLivenessContainer?: ViewContainerRef;
|
|
83
|
+
|
|
84
|
+
@Output() verificationComplete = new EventEmitter<LivenessResult>();
|
|
85
|
+
|
|
86
|
+
readonly currentStep = signal<FlowStep>('intro');
|
|
87
|
+
config!: LivenessConfig;
|
|
88
|
+
readonly result = signal<LivenessResult | null>(null);
|
|
89
|
+
readonly processingProgress = signal(0);
|
|
90
|
+
capturedFrames: string[] = [];
|
|
91
|
+
videoBlob?: Blob;
|
|
92
|
+
attemptCount = 0;
|
|
93
|
+
referencePose?: ReferencePose;
|
|
94
|
+
selectedPose?: SelectedPoseInfo;
|
|
95
|
+
|
|
96
|
+
verificationSessionId = '';
|
|
97
|
+
gestureFrameHash = '';
|
|
98
|
+
faceTecResult: FaceTecScanResult | null = null;
|
|
99
|
+
awsFaceLivenessResult: AwsFaceLivenessScanResult | null = null;
|
|
100
|
+
deviceKey = 'd0WBygutVzX1Iq7plu7zQMC4CncoA6cq';
|
|
101
|
+
|
|
102
|
+
awsRegion = 'us-west-2';
|
|
103
|
+
useAwsFaceLiveness = false;
|
|
104
|
+
|
|
105
|
+
private resultSubject = new Subject<LivenessResult>();
|
|
106
|
+
public result$: Observable<LivenessResult> = this.resultSubject.asObservable();
|
|
107
|
+
private awsOutputSubs: Subscription[] = [];
|
|
108
|
+
private awsComponentRef: ComponentRef<unknown> | null = null;
|
|
109
|
+
private awsComponentCreated = false;
|
|
110
|
+
private awsWatchdogTimer: ReturnType<typeof setTimeout> | null = null;
|
|
111
|
+
|
|
112
|
+
constructor(
|
|
113
|
+
private configService: LivenessConfigService,
|
|
114
|
+
private orchestrator: LivenessOrchestratorService,
|
|
115
|
+
private poseSelection: PoseSelectionService,
|
|
116
|
+
private verificationApi: VerificationApiService,
|
|
117
|
+
private ngZone: NgZone,
|
|
118
|
+
private cdr: ChangeDetectorRef,
|
|
119
|
+
private appRef: ApplicationRef,
|
|
120
|
+
@Optional() @Inject(AWS_FACE_LIVENESS_COMPONENT) public awsFaceLivenessComponent: Type<unknown> | null
|
|
121
|
+
) {
|
|
122
|
+
flowLog('constructor', 'Component created');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
ngOnInit(): void {
|
|
126
|
+
const libConfig = this.configService.getConfig();
|
|
127
|
+
this.config = {
|
|
128
|
+
...libConfig,
|
|
129
|
+
backends: libConfig.backends || (['amazon'] as LivenessBackend[])
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (libConfig.useAwsFaceLiveness === true) {
|
|
133
|
+
this.useAwsFaceLiveness = true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
flowLog('ngOnInit', 'Config loaded', {
|
|
137
|
+
backends: this.config.backends,
|
|
138
|
+
apiUrl: (libConfig as any).apiUrl,
|
|
139
|
+
showIntroScreen: libConfig.showIntroScreen,
|
|
140
|
+
enableVideoRecording: libConfig.enableVideoRecording,
|
|
141
|
+
autoCapture: libConfig.autoCapture,
|
|
142
|
+
customPose: !!libConfig.customPose,
|
|
143
|
+
poseId: libConfig.poseId,
|
|
144
|
+
useAwsFaceLiveness: this.useAwsFaceLiveness,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const skipIntro = this.shouldSkipIntro();
|
|
148
|
+
flowLog('ngOnInit', `shouldSkipIntro=${skipIntro}`);
|
|
149
|
+
|
|
150
|
+
if (skipIntro) {
|
|
151
|
+
this.currentStep.set('permission');
|
|
152
|
+
this.selectedPose = this.poseSelection.getSelectedPose() ?? undefined;
|
|
153
|
+
flowLog('ngOnInit', 'Skipping intro, selected pose:', this.selectedPose?.pose?.name ?? 'none');
|
|
154
|
+
}
|
|
155
|
+
flowLog('ngOnInit', `Initial step: ${this.currentStep()}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
ngAfterViewChecked(): void {
|
|
159
|
+
if (this.currentStep() === 'aws-liveness' && !this.awsComponentCreated) {
|
|
160
|
+
if (this.awsFaceLivenessComponent && this.awsLivenessContainer) {
|
|
161
|
+
this.createDynamicAwsComponent();
|
|
162
|
+
} else if (this.awsFaceLivenessComponent && !this.awsLivenessContainer) {
|
|
163
|
+
flowLog('ngAfterViewChecked', 'aws-liveness step active but ViewContainerRef not yet available, scheduling retry');
|
|
164
|
+
if (!this._awsRetryScheduled) {
|
|
165
|
+
this._awsRetryScheduled = true;
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
this._awsRetryScheduled = false;
|
|
168
|
+
if (this.currentStep() === 'aws-liveness' && !this.awsComponentCreated && this.awsLivenessContainer) {
|
|
169
|
+
this.createDynamicAwsComponent();
|
|
170
|
+
} else if (this.currentStep() === 'aws-liveness' && !this.awsComponentCreated) {
|
|
171
|
+
flowWarn('ngAfterViewChecked', 'ViewContainerRef still not available after retry, forcing detectChanges');
|
|
172
|
+
this.cdr.detectChanges();
|
|
173
|
+
}
|
|
174
|
+
}, 50);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.currentStep() !== 'aws-liveness' && this.awsComponentCreated) {
|
|
180
|
+
this.destroyDynamicAwsComponent();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
private _awsRetryScheduled = false;
|
|
184
|
+
|
|
185
|
+
ngOnDestroy(): void {
|
|
186
|
+
flowLog('ngOnDestroy', 'Component destroying');
|
|
187
|
+
this.clearAwsWatchdog();
|
|
188
|
+
this.destroyDynamicAwsComponent();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private clearAwsWatchdog(): void {
|
|
192
|
+
if (this.awsWatchdogTimer) {
|
|
193
|
+
clearTimeout(this.awsWatchdogTimer);
|
|
194
|
+
this.awsWatchdogTimer = null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private startAwsWatchdog(): void {
|
|
199
|
+
this.clearAwsWatchdog();
|
|
200
|
+
this.awsWatchdogTimer = setTimeout(() => {
|
|
201
|
+
if (this.currentStep() === 'aws-liveness') {
|
|
202
|
+
flowWarn('watchdog', 'AWS Face Liveness did not respond within 30s — showing error');
|
|
203
|
+
this.ngZone.run(() => {
|
|
204
|
+
this.setResultAndNotify({
|
|
205
|
+
success: false,
|
|
206
|
+
confidence: 0,
|
|
207
|
+
verifiedImage: '',
|
|
208
|
+
error: 'Camera or liveness check timed out. Please check:\n• Camera is enabled in System Settings\n• Browser has camera permission\n• No other app is using the camera\n• Try refreshing the page or using Chrome/Edge',
|
|
209
|
+
metadata: {
|
|
210
|
+
timestamp: new Date().toISOString(),
|
|
211
|
+
backend: 'amazon' as LivenessBackend,
|
|
212
|
+
confidence: 0,
|
|
213
|
+
attemptNumber: this.attemptCount
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
this.currentStep.set('result');
|
|
217
|
+
this.cdr.markForCheck();
|
|
218
|
+
this.cdr.detectChanges();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}, 30000);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private createDynamicAwsComponent(): void {
|
|
225
|
+
if (!this.awsLivenessContainer || !this.awsFaceLivenessComponent) return;
|
|
226
|
+
|
|
227
|
+
flowLog('awsComponent', 'Creating dynamic AWS Face Liveness component');
|
|
228
|
+
|
|
229
|
+
this.awsLivenessContainer.clear();
|
|
230
|
+
|
|
231
|
+
this.awsComponentRef = this.awsLivenessContainer.createComponent(this.awsFaceLivenessComponent);
|
|
232
|
+
this.awsComponentCreated = true;
|
|
233
|
+
|
|
234
|
+
const instance = this.awsComponentRef.instance as {
|
|
235
|
+
region?: string;
|
|
236
|
+
autoStart?: boolean;
|
|
237
|
+
showCancelButton?: boolean;
|
|
238
|
+
scanComplete?: { subscribe: (fn: (r: AwsFaceLivenessScanResult) => void) => Subscription };
|
|
239
|
+
scanCancelled?: { subscribe: (fn: () => void) => Subscription };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
if ('region' in instance) instance.region = this.awsRegion;
|
|
243
|
+
if ('autoStart' in instance) instance.autoStart = true;
|
|
244
|
+
if ('showCancelButton' in instance) instance.showCancelButton = true;
|
|
245
|
+
|
|
246
|
+
this.awsOutputSubs.forEach((s) => s.unsubscribe());
|
|
247
|
+
this.awsOutputSubs = [];
|
|
248
|
+
|
|
249
|
+
if (instance.scanComplete) {
|
|
250
|
+
this.awsOutputSubs.push(
|
|
251
|
+
instance.scanComplete.subscribe((r) => this.onAwsFaceLivenessComplete(r))
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (instance.scanCancelled) {
|
|
255
|
+
this.awsOutputSubs.push(
|
|
256
|
+
instance.scanCancelled.subscribe(() => this.onAwsFaceLivenessCancelled())
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.awsComponentRef.changeDetectorRef.detectChanges();
|
|
261
|
+
|
|
262
|
+
flowLog('awsComponent', 'Dynamic AWS Face Liveness component created and configured');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private destroyDynamicAwsComponent(): void {
|
|
266
|
+
this.awsOutputSubs.forEach((s) => s.unsubscribe());
|
|
267
|
+
this.awsOutputSubs = [];
|
|
268
|
+
|
|
269
|
+
if (this.awsComponentRef) {
|
|
270
|
+
this.awsComponentRef.destroy();
|
|
271
|
+
this.awsComponentRef = null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (this.awsLivenessContainer) {
|
|
275
|
+
this.awsLivenessContainer.clear();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.awsComponentCreated = false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
get awsFaceLivenessInputs(): { region: string; autoStart: boolean; showCancelButton: boolean } {
|
|
282
|
+
return { region: this.awsRegion, autoStart: true, showCancelButton: true };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
startLivenessFlow(config?: Partial<LivenessConfig>): Observable<LivenessResult> {
|
|
286
|
+
flowLog('startLivenessFlow', 'Called', config);
|
|
287
|
+
if (config) {
|
|
288
|
+
this.configService.updateConfig(config);
|
|
289
|
+
const libConfig = this.configService.getConfig();
|
|
290
|
+
this.config = {
|
|
291
|
+
...libConfig,
|
|
292
|
+
backends: libConfig.backends || (['amazon'] as LivenessBackend[])
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
if (this.shouldSkipIntro()) {
|
|
296
|
+
this.currentStep.set('permission');
|
|
297
|
+
this.selectedPose = this.poseSelection.getSelectedPose() ?? undefined;
|
|
298
|
+
} else {
|
|
299
|
+
this.currentStep.set('intro');
|
|
300
|
+
}
|
|
301
|
+
this.attemptCount = 0;
|
|
302
|
+
return this.result$;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
private shouldSkipIntro(): boolean {
|
|
306
|
+
const verificationConfig = this.configService.getConfig();
|
|
307
|
+
if (verificationConfig.showIntroScreen !== false) return false;
|
|
308
|
+
return !!(
|
|
309
|
+
verificationConfig.customPose ||
|
|
310
|
+
(verificationConfig.poseId != null && verificationConfig.poseId > 0) ||
|
|
311
|
+
verificationConfig.useAwsFaceLiveness
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
get poseSpecificMode(): boolean {
|
|
316
|
+
const c = this.configService.getConfig();
|
|
317
|
+
return !!(c.customPose || (c.poseId != null && c.poseId > 0));
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
onReferencePoseUploaded(pose: ReferencePose): void {
|
|
321
|
+
this.referencePose = pose;
|
|
322
|
+
flowLog('event', 'Reference pose uploaded:', pose.poseDescription);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
onPoseSelected(pose: SelectedPoseInfo): void {
|
|
326
|
+
this.selectedPose = pose;
|
|
327
|
+
flowLog('event', 'Pose selected:', pose.pose.name);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
onAwsFaceLivenessToggled(enabled: boolean): void {
|
|
331
|
+
this.useAwsFaceLiveness = enabled;
|
|
332
|
+
flowLog('event', 'AWS Face Liveness toggled:', enabled);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
onStartClicked(): void {
|
|
336
|
+
flowLog('step', 'Start clicked → permission');
|
|
337
|
+
this.currentStep.set('permission');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async onPermissionGranted(): Promise<void> {
|
|
341
|
+
if (this.useAwsFaceLiveness) {
|
|
342
|
+
flowLog('step', 'Permission granted → skipping preview for AWS Face Liveness (static flow)');
|
|
343
|
+
await this.startAwsLivenessDirectly();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
flowLog('step', 'Permission granted → preview');
|
|
347
|
+
this.currentStep.set('preview');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
private async startAwsLivenessDirectly(): Promise<void> {
|
|
351
|
+
flowLog('awsDirect', '=== STARTING AWS FACE LIVENESS DIRECTLY (no pose matching) ===');
|
|
352
|
+
try {
|
|
353
|
+
const config = this.configService.getConfig();
|
|
354
|
+
const backends = config.backends || ['amazon'];
|
|
355
|
+
const hasFacetec = backends.includes('facetec');
|
|
356
|
+
|
|
357
|
+
const plan: Array<{ kind: 'gesture' | 'facetec'; pose?: string }> = [
|
|
358
|
+
{ kind: 'gesture' as const, pose: 'hello' }
|
|
359
|
+
];
|
|
360
|
+
if (hasFacetec) {
|
|
361
|
+
plan.push({ kind: 'facetec' as const });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
flowLog('awsDirect', '>>> Creating verification session...');
|
|
365
|
+
const session = await firstValueFrom(this.verificationApi.startSession({ plan }));
|
|
366
|
+
flowLog('awsDirect', '<<< startSession response:', session);
|
|
367
|
+
if (!session) throw new Error('Failed to create verification session');
|
|
368
|
+
|
|
369
|
+
this.verificationSessionId = session.sessionId;
|
|
370
|
+
flowLog('awsDirect', `Session ID: ${this.verificationSessionId}`);
|
|
371
|
+
|
|
372
|
+
this.ngZone.run(() => {
|
|
373
|
+
this.currentStep.set('aws-liveness');
|
|
374
|
+
this.startAwsWatchdog();
|
|
375
|
+
this.cdr.markForCheck();
|
|
376
|
+
this.cdr.detectChanges();
|
|
377
|
+
this.appRef.tick();
|
|
378
|
+
flowLog('awsDirect', `Step set to: ${this.currentStep()}`);
|
|
379
|
+
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
const awsEl = document.querySelector('facecog-aws-face-liveness');
|
|
382
|
+
flowLog('awsDirect', `Post-tick DOM check: facecog-aws-face-liveness ${awsEl ? 'FOUND' : 'NOT FOUND'}`);
|
|
383
|
+
if (!awsEl) {
|
|
384
|
+
flowWarn('awsDirect', 'Component not in DOM after tick, forcing another detectChanges cycle');
|
|
385
|
+
this.cdr.markForCheck();
|
|
386
|
+
this.cdr.detectChanges();
|
|
387
|
+
this.appRef.tick();
|
|
388
|
+
}
|
|
389
|
+
}, 100);
|
|
390
|
+
});
|
|
391
|
+
} catch (error: unknown) {
|
|
392
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
393
|
+
flowError('awsDirect', `!!! ERROR: ${err.message}`, err);
|
|
394
|
+
this.ngZone.run(() => {
|
|
395
|
+
this.setResultAndNotify({
|
|
396
|
+
success: false,
|
|
397
|
+
confidence: 0,
|
|
398
|
+
verifiedImage: '',
|
|
399
|
+
error: err.message || 'Failed to start AWS Face Liveness',
|
|
400
|
+
metadata: {
|
|
401
|
+
timestamp: new Date().toISOString(),
|
|
402
|
+
backend: 'amazon' as LivenessBackend,
|
|
403
|
+
confidence: 0,
|
|
404
|
+
attemptNumber: this.attemptCount
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
this.currentStep.set('result');
|
|
408
|
+
this.cdr.markForCheck();
|
|
409
|
+
this.cdr.detectChanges();
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
onPermissionDenied(): void {
|
|
415
|
+
flowLog('step', 'Permission denied → showing result screen with error');
|
|
416
|
+
const permissionError = {
|
|
417
|
+
success: false,
|
|
418
|
+
confidence: 0,
|
|
419
|
+
verifiedImage: '',
|
|
420
|
+
error: 'Camera permission denied. Please check:\n• Camera is enabled in System Settings\n• Browser has camera permission\n• No other app is using the camera\n• Try refreshing the page',
|
|
421
|
+
metadata: {
|
|
422
|
+
timestamp: new Date().toISOString(),
|
|
423
|
+
backend: 'amazon' as LivenessBackend,
|
|
424
|
+
confidence: 0,
|
|
425
|
+
attemptNumber: this.attemptCount
|
|
426
|
+
}
|
|
427
|
+
};
|
|
428
|
+
this.setResultAndNotify(permissionError);
|
|
429
|
+
this.currentStep.set('result');
|
|
430
|
+
this.cdr.markForCheck();
|
|
431
|
+
this.cdr.detectChanges();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async onCaptureComplete(data: { frames: string[]; videoBlob?: Blob }): Promise<void> {
|
|
435
|
+
flowLog('capture', `=== CAPTURE COMPLETE === frames=${data.frames.length}, hasVideo=${!!data.videoBlob}`);
|
|
436
|
+
this.capturedFrames = data.frames;
|
|
437
|
+
this.videoBlob = data.videoBlob;
|
|
438
|
+
|
|
439
|
+
const config = this.configService.getConfig();
|
|
440
|
+
const backends = config.backends || ['amazon'];
|
|
441
|
+
flowLog('capture', `Active backends from config: ${JSON.stringify(backends)}`);
|
|
442
|
+
flowLog('capture', `useAwsFaceLiveness: ${this.useAwsFaceLiveness}`);
|
|
443
|
+
flowLog('capture', `API URL from config: ${(config as any).apiUrl}`);
|
|
444
|
+
|
|
445
|
+
const hasFacetec = backends.includes('facetec');
|
|
446
|
+
const plan: Array<{ kind: 'gesture' | 'facetec'; pose?: string }> = [
|
|
447
|
+
{ kind: 'gesture' as const, pose: 'hello' }
|
|
448
|
+
];
|
|
449
|
+
if (hasFacetec) {
|
|
450
|
+
plan.push({ kind: 'facetec' as const });
|
|
451
|
+
}
|
|
452
|
+
flowLog('capture', `Verification plan (hasFacetec=${hasFacetec}):`, plan);
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
flowLog('capture', '>>> Calling verificationApi.startSession()...');
|
|
456
|
+
const session = await firstValueFrom(this.verificationApi.startSession({ plan }));
|
|
457
|
+
flowLog('capture', '<<< startSession response:', session);
|
|
458
|
+
if (!session) throw new Error('Failed to create verification session');
|
|
459
|
+
|
|
460
|
+
this.verificationSessionId = session.sessionId;
|
|
461
|
+
flowLog('capture', `Session ID: ${this.verificationSessionId}`);
|
|
462
|
+
|
|
463
|
+
flowLog('capture', '>>> Calling verificationApi.verifyGestures()...');
|
|
464
|
+
const gestureResponse = await firstValueFrom(
|
|
465
|
+
this.verificationApi.verifyGestures({
|
|
466
|
+
sessionId: this.verificationSessionId,
|
|
467
|
+
stepIndex: 0,
|
|
468
|
+
frames: this.capturedFrames,
|
|
469
|
+
pose: 'hello'
|
|
470
|
+
})
|
|
471
|
+
);
|
|
472
|
+
flowLog('capture', '<<< verifyGestures response:', gestureResponse);
|
|
473
|
+
|
|
474
|
+
if (!gestureResponse) throw new Error('Gesture verification failed');
|
|
475
|
+
|
|
476
|
+
this.gestureFrameHash = gestureResponse.frameHash || '';
|
|
477
|
+
flowLog('capture', `Gesture frame hash: ${this.gestureFrameHash}`);
|
|
478
|
+
|
|
479
|
+
if (this.useAwsFaceLiveness) {
|
|
480
|
+
flowLog('capture', 'Route: AWS Face Liveness path');
|
|
481
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
482
|
+
this.currentStep.set('aws-liveness');
|
|
483
|
+
flowLog('capture', `Step set to: ${this.currentStep()}`);
|
|
484
|
+
} else if (hasFacetec) {
|
|
485
|
+
flowLog('capture', 'Route: FaceTec path → setting step to facetec');
|
|
486
|
+
this.currentStep.set('facetec');
|
|
487
|
+
flowLog('capture', `Step set to: ${this.currentStep()}`);
|
|
488
|
+
} else {
|
|
489
|
+
flowLog('capture', 'Route: Multi-backend processing path (no facetec in backends)');
|
|
490
|
+
this.currentStep.set('processing');
|
|
491
|
+
flowLog('capture', `Step set to: ${this.currentStep()}`);
|
|
492
|
+
await this.processMultiBackendVerification();
|
|
493
|
+
}
|
|
494
|
+
} catch (error: unknown) {
|
|
495
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
496
|
+
flowError('capture', `!!! CAPTURE ERROR: ${err.message}`, err);
|
|
497
|
+
this.setResultAndNotify({
|
|
498
|
+
success: false,
|
|
499
|
+
confidence: 0,
|
|
500
|
+
verifiedImage: this.capturedFrames[0] || '',
|
|
501
|
+
error: err.message || 'Hybrid verification failed',
|
|
502
|
+
metadata: {
|
|
503
|
+
timestamp: new Date().toISOString(),
|
|
504
|
+
backend: 'facetec' as LivenessBackend,
|
|
505
|
+
confidence: 0,
|
|
506
|
+
attemptNumber: this.attemptCount
|
|
507
|
+
}
|
|
508
|
+
});
|
|
509
|
+
this.currentStep.set('result');
|
|
510
|
+
flowLog('capture', `Error handled, step set to: ${this.currentStep()}`);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async onFaceTecScanComplete(result: FaceTecScanResult): Promise<void> {
|
|
515
|
+
flowLog('facetec', '=== FACETEC SCAN COMPLETE ===', { success: result.success, error: result.error });
|
|
516
|
+
this.faceTecResult = result;
|
|
517
|
+
|
|
518
|
+
if (!result.success) {
|
|
519
|
+
flowLog('facetec', 'FaceTec scan failed, going to result');
|
|
520
|
+
this.setResultAndNotify({
|
|
521
|
+
success: false,
|
|
522
|
+
confidence: 0,
|
|
523
|
+
verifiedImage: this.capturedFrames[0] || '',
|
|
524
|
+
error: result.error || 'FaceTec scan failed',
|
|
525
|
+
metadata: {
|
|
526
|
+
timestamp: new Date().toISOString(),
|
|
527
|
+
backend: 'facetec' as LivenessBackend,
|
|
528
|
+
confidence: 0,
|
|
529
|
+
attemptNumber: this.attemptCount
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
this.currentStep.set('result');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
flowLog('facetec', 'FaceTec scan succeeded, going to processing');
|
|
537
|
+
this.currentStep.set('processing');
|
|
538
|
+
await this.processHybridVerification();
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
onFaceTecScanCancelled(): void {
|
|
542
|
+
flowLog('facetec', 'FaceTec scan cancelled');
|
|
543
|
+
this.onCancel();
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async onAwsFaceLivenessComplete(result: AwsFaceLivenessScanResult): Promise<void> {
|
|
547
|
+
flowLog('aws', '=== AWS FACE LIVENESS COMPLETE ===', { success: result.success, error: result.error });
|
|
548
|
+
this.clearAwsWatchdog();
|
|
549
|
+
this.awsFaceLivenessResult = result;
|
|
550
|
+
this.awsOutputSubs.forEach((s) => s.unsubscribe());
|
|
551
|
+
this.awsOutputSubs = [];
|
|
552
|
+
|
|
553
|
+
if (!result.success) {
|
|
554
|
+
flowLog('aws', 'AWS check failed, going to result');
|
|
555
|
+
this.setResultAndNotify({
|
|
556
|
+
success: false,
|
|
557
|
+
confidence: 0,
|
|
558
|
+
verifiedImage: this.capturedFrames[0] || '',
|
|
559
|
+
error: result.error || 'AWS Face Liveness check failed',
|
|
560
|
+
metadata: {
|
|
561
|
+
timestamp: new Date().toISOString(),
|
|
562
|
+
backend: 'amazon' as LivenessBackend,
|
|
563
|
+
confidence: 0,
|
|
564
|
+
attemptNumber: this.attemptCount
|
|
565
|
+
}
|
|
566
|
+
});
|
|
567
|
+
this.currentStep.set('result');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
flowLog('aws', 'AWS check succeeded, going to processing');
|
|
572
|
+
this.currentStep.set('processing');
|
|
573
|
+
await this.processAwsFaceLivenessVerification();
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
onAwsFaceLivenessCancelled(): void {
|
|
577
|
+
flowLog('aws', 'AWS Face Liveness cancelled');
|
|
578
|
+
this.clearAwsWatchdog();
|
|
579
|
+
this.awsOutputSubs.forEach((s) => s.unsubscribe());
|
|
580
|
+
this.awsOutputSubs = [];
|
|
581
|
+
this.onCancel();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private async processAwsFaceLivenessVerification(): Promise<void> {
|
|
585
|
+
flowLog('processAws', '=== PROCESSING AWS LIVENESS VERIFICATION ===');
|
|
586
|
+
this.attemptCount++;
|
|
587
|
+
const primaryFrame = this.capturedFrames[0] || '';
|
|
588
|
+
const hasPreviewCapture = this.capturedFrames.length > 0;
|
|
589
|
+
|
|
590
|
+
const progressInterval = setInterval(() => {
|
|
591
|
+
this.processingProgress.update(p => Math.min(p + 10, 90));
|
|
592
|
+
}, 200);
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
const awsLivenessResult = this.awsFaceLivenessResult!;
|
|
596
|
+
let videoUrl: string | undefined;
|
|
597
|
+
|
|
598
|
+
if (hasPreviewCapture) {
|
|
599
|
+
flowLog('processAws', 'Has preview capture frames, running orchestrator verification...');
|
|
600
|
+
flowLog('processAws', '>>> Calling orchestrator.verifyLiveness()...');
|
|
601
|
+
const backendResponse = await new Promise<{ isLive: boolean; confidence: number }>((resolve, reject) => {
|
|
602
|
+
this.orchestrator.verifyLiveness(primaryFrame, this.videoBlob).subscribe({ next: resolve, error: reject });
|
|
603
|
+
});
|
|
604
|
+
flowLog('processAws', '<<< verifyLiveness response:', backendResponse);
|
|
605
|
+
|
|
606
|
+
const multiBackendResult = this.orchestrator.multiBackendResult;
|
|
607
|
+
flowLog('processAws', 'Multi-backend result available:', !!multiBackendResult);
|
|
608
|
+
|
|
609
|
+
const uploadSessionId = multiBackendResult?.primary?.metadata?.sessionId || this.verificationSessionId;
|
|
610
|
+
videoUrl = await this.uploadMediaToS3(uploadSessionId, primaryFrame);
|
|
611
|
+
|
|
612
|
+
clearInterval(progressInterval);
|
|
613
|
+
this.processingProgress.set(100);
|
|
614
|
+
|
|
615
|
+
const isLive = awsLivenessResult.isLive && backendResponse.isLive;
|
|
616
|
+
const confidence = (awsLivenessResult.confidence + backendResponse.confidence) / 2;
|
|
617
|
+
|
|
618
|
+
flowLog('processAws', `Final (hybrid): isLive=${isLive}, confidence=${confidence}`);
|
|
619
|
+
|
|
620
|
+
this.setResultAndNotify({
|
|
621
|
+
success: isLive,
|
|
622
|
+
confidence,
|
|
623
|
+
verifiedImage: primaryFrame,
|
|
624
|
+
videoBlob: this.videoBlob,
|
|
625
|
+
videoUrl,
|
|
626
|
+
multiBackendResults: multiBackendResult || undefined,
|
|
627
|
+
metadata: {
|
|
628
|
+
timestamp: new Date().toISOString(),
|
|
629
|
+
backend: 'amazon',
|
|
630
|
+
confidence,
|
|
631
|
+
attemptNumber: this.attemptCount,
|
|
632
|
+
sessionId: this.verificationSessionId
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
} else {
|
|
636
|
+
flowLog('processAws', 'Direct AWS flow (no preview capture), using AWS result only');
|
|
637
|
+
|
|
638
|
+
const awsVideoBlob = awsLivenessResult.videoBlob;
|
|
639
|
+
if (awsVideoBlob) {
|
|
640
|
+
flowLog('processAws', `AWS video blob available: ${(awsVideoBlob.size / 1024 / 1024).toFixed(2)} MB`);
|
|
641
|
+
this.videoBlob = awsVideoBlob;
|
|
642
|
+
videoUrl = await this.uploadMediaToS3(this.verificationSessionId, '');
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
clearInterval(progressInterval);
|
|
646
|
+
this.processingProgress.set(100);
|
|
647
|
+
|
|
648
|
+
flowLog('processAws', `Final (AWS-only): isLive=${awsLivenessResult.isLive}, confidence=${awsLivenessResult.confidence}`);
|
|
649
|
+
|
|
650
|
+
this.setResultAndNotify({
|
|
651
|
+
success: awsLivenessResult.isLive,
|
|
652
|
+
confidence: awsLivenessResult.confidence,
|
|
653
|
+
verifiedImage: '',
|
|
654
|
+
videoBlob: awsVideoBlob,
|
|
655
|
+
videoUrl,
|
|
656
|
+
startVideoCheckClickedAt: awsLivenessResult.startVideoCheckClickedAt,
|
|
657
|
+
recordingStartedAt: awsLivenessResult.recordingStartedAt,
|
|
658
|
+
metadata: {
|
|
659
|
+
timestamp: new Date().toISOString(),
|
|
660
|
+
backend: 'amazon',
|
|
661
|
+
confidence: awsLivenessResult.confidence,
|
|
662
|
+
attemptNumber: this.attemptCount,
|
|
663
|
+
sessionId: this.verificationSessionId
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
this.currentStep.set('result');
|
|
669
|
+
flowLog('processAws', `Step set to: ${this.currentStep()}`);
|
|
670
|
+
} catch (error: unknown) {
|
|
671
|
+
clearInterval(progressInterval);
|
|
672
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
673
|
+
flowError('processAws', `!!! ERROR: ${err.message}`, err);
|
|
674
|
+
this.setResultAndNotify({
|
|
675
|
+
success: false,
|
|
676
|
+
confidence: 0,
|
|
677
|
+
verifiedImage: primaryFrame,
|
|
678
|
+
error: err.message || 'AWS Face Liveness verification failed',
|
|
679
|
+
metadata: {
|
|
680
|
+
timestamp: new Date().toISOString(),
|
|
681
|
+
backend: 'amazon' as LivenessBackend,
|
|
682
|
+
confidence: 0,
|
|
683
|
+
attemptNumber: this.attemptCount
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
this.currentStep.set('result');
|
|
687
|
+
flowLog('processAws', `Error handled, step set to: ${this.currentStep()}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private async processHybridVerification(): Promise<void> {
|
|
692
|
+
flowLog('processHybrid', '=== PROCESSING HYBRID VERIFICATION ===');
|
|
693
|
+
this.attemptCount++;
|
|
694
|
+
const primaryFrame = this.capturedFrames[0];
|
|
695
|
+
|
|
696
|
+
const progressInterval = setInterval(() => {
|
|
697
|
+
this.processingProgress.update(p => Math.min(p + 10, 90));
|
|
698
|
+
}, 200);
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
flowLog('processHybrid', '>>> Calling orchestrator.verifyLiveness()...');
|
|
702
|
+
await new Promise((resolve, reject) => {
|
|
703
|
+
this.orchestrator.verifyLiveness(primaryFrame, this.videoBlob).subscribe({ next: resolve, error: reject });
|
|
704
|
+
});
|
|
705
|
+
flowLog('processHybrid', '<<< verifyLiveness completed');
|
|
706
|
+
|
|
707
|
+
const multiBackendResult = this.orchestrator.multiBackendResult;
|
|
708
|
+
flowLog('processHybrid', 'Multi-backend result available:', !!multiBackendResult);
|
|
709
|
+
|
|
710
|
+
flowLog('processHybrid', '>>> Calling verificationApi.verifyFaceTec()...');
|
|
711
|
+
const faceTecResponse = await firstValueFrom(
|
|
712
|
+
this.verificationApi.verifyFaceTec({
|
|
713
|
+
sessionId: this.verificationSessionId,
|
|
714
|
+
stepIndex: 1,
|
|
715
|
+
faceScanBlob: this.faceTecResult!.faceScan,
|
|
716
|
+
frameHash: this.gestureFrameHash
|
|
717
|
+
})
|
|
718
|
+
);
|
|
719
|
+
flowLog('processHybrid', '<<< verifyFaceTec response:', faceTecResponse);
|
|
720
|
+
|
|
721
|
+
if (!faceTecResponse) throw new Error('FaceTec verification failed');
|
|
722
|
+
|
|
723
|
+
flowLog('processHybrid', '>>> Calling verificationApi.finalize()...');
|
|
724
|
+
const finalResult = await firstValueFrom(this.verificationApi.finalize(this.verificationSessionId));
|
|
725
|
+
flowLog('processHybrid', '<<< finalize response:', finalResult);
|
|
726
|
+
if (!finalResult) throw new Error('Session finalization failed');
|
|
727
|
+
|
|
728
|
+
const uploadSessionId = multiBackendResult?.primary?.metadata?.sessionId || this.verificationSessionId;
|
|
729
|
+
const videoUrl = await this.uploadMediaToS3(uploadSessionId, primaryFrame);
|
|
730
|
+
|
|
731
|
+
clearInterval(progressInterval);
|
|
732
|
+
this.processingProgress.set(100);
|
|
733
|
+
|
|
734
|
+
flowLog('processHybrid', `Final: allPassed=${finalResult.allPassed}, confidence=${finalResult.confidence}`);
|
|
735
|
+
|
|
736
|
+
this.setResultAndNotify({
|
|
737
|
+
success: finalResult.allPassed,
|
|
738
|
+
confidence: finalResult.confidence,
|
|
739
|
+
verifiedImage: this.capturedFrames[0] || '',
|
|
740
|
+
videoBlob: this.videoBlob,
|
|
741
|
+
videoUrl,
|
|
742
|
+
multiBackendResults: multiBackendResult || undefined,
|
|
743
|
+
metadata: {
|
|
744
|
+
timestamp: new Date().toISOString(),
|
|
745
|
+
backend: 'facetec' as LivenessBackend,
|
|
746
|
+
confidence: finalResult.confidence,
|
|
747
|
+
attemptNumber: this.attemptCount,
|
|
748
|
+
sessionId: this.verificationSessionId,
|
|
749
|
+
gestureResult: finalResult.gestureResults?.[0],
|
|
750
|
+
facetecResult: finalResult.facetecResult
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
this.currentStep.set('result');
|
|
755
|
+
flowLog('processHybrid', `Step set to: ${this.currentStep()}`);
|
|
756
|
+
} catch (error: unknown) {
|
|
757
|
+
clearInterval(progressInterval);
|
|
758
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
759
|
+
flowError('processHybrid', `!!! ERROR: ${err.message}`, err);
|
|
760
|
+
this.setResultAndNotify({
|
|
761
|
+
success: false,
|
|
762
|
+
confidence: 0,
|
|
763
|
+
verifiedImage: this.capturedFrames[0] || '',
|
|
764
|
+
error: err.message || 'Hybrid verification failed',
|
|
765
|
+
metadata: {
|
|
766
|
+
timestamp: new Date().toISOString(),
|
|
767
|
+
backend: 'facetec' as LivenessBackend,
|
|
768
|
+
confidence: 0,
|
|
769
|
+
attemptNumber: this.attemptCount
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
this.currentStep.set('result');
|
|
773
|
+
flowLog('processHybrid', `Error handled, step set to: ${this.currentStep()}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
private async processMultiBackendVerification(): Promise<void> {
|
|
778
|
+
flowLog('processMulti', '=== PROCESSING MULTI-BACKEND VERIFICATION (no facetec) ===');
|
|
779
|
+
this.attemptCount++;
|
|
780
|
+
const primaryFrame = this.capturedFrames[0];
|
|
781
|
+
|
|
782
|
+
const progressInterval = setInterval(() => {
|
|
783
|
+
this.processingProgress.update(p => Math.min(p + 10, 90));
|
|
784
|
+
}, 200);
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
flowLog('processMulti', '>>> Calling orchestrator.verifyLiveness()...');
|
|
788
|
+
const backendResponse = await new Promise<{ isLive: boolean; confidence: number }>((resolve, reject) => {
|
|
789
|
+
this.orchestrator.verifyLiveness(primaryFrame, this.videoBlob).subscribe({ next: resolve, error: reject });
|
|
790
|
+
});
|
|
791
|
+
flowLog('processMulti', '<<< verifyLiveness response:', backendResponse);
|
|
792
|
+
|
|
793
|
+
const multiBackendResult = this.orchestrator.multiBackendResult;
|
|
794
|
+
flowLog('processMulti', 'Multi-backend result available:', !!multiBackendResult);
|
|
795
|
+
|
|
796
|
+
flowLog('processMulti', '>>> Calling verificationApi.finalize()...');
|
|
797
|
+
const finalResult = await firstValueFrom(this.verificationApi.finalize(this.verificationSessionId));
|
|
798
|
+
flowLog('processMulti', '<<< finalize response:', finalResult);
|
|
799
|
+
if (!finalResult) throw new Error('Session finalization failed');
|
|
800
|
+
|
|
801
|
+
const uploadSessionId = multiBackendResult?.primary?.metadata?.sessionId || this.verificationSessionId;
|
|
802
|
+
const videoUrl = await this.uploadMediaToS3(uploadSessionId, primaryFrame);
|
|
803
|
+
|
|
804
|
+
clearInterval(progressInterval);
|
|
805
|
+
this.processingProgress.set(100);
|
|
806
|
+
|
|
807
|
+
flowLog('processMulti', `Final: allPassed=${finalResult.allPassed}, confidence=${finalResult.confidence}`);
|
|
808
|
+
|
|
809
|
+
this.setResultAndNotify({
|
|
810
|
+
success: finalResult.allPassed,
|
|
811
|
+
confidence: finalResult.confidence,
|
|
812
|
+
verifiedImage: primaryFrame,
|
|
813
|
+
videoBlob: this.videoBlob,
|
|
814
|
+
videoUrl,
|
|
815
|
+
multiBackendResults: multiBackendResult || undefined,
|
|
816
|
+
metadata: {
|
|
817
|
+
timestamp: new Date().toISOString(),
|
|
818
|
+
backend: 'amazon',
|
|
819
|
+
confidence: finalResult.confidence,
|
|
820
|
+
attemptNumber: this.attemptCount,
|
|
821
|
+
sessionId: this.verificationSessionId,
|
|
822
|
+
gestureResult: finalResult.gestureResults?.[0]
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
this.currentStep.set('result');
|
|
827
|
+
flowLog('processMulti', `Step set to: ${this.currentStep()}`);
|
|
828
|
+
} catch (error: unknown) {
|
|
829
|
+
clearInterval(progressInterval);
|
|
830
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
831
|
+
flowError('processMulti', `!!! ERROR: ${err.message}`, err);
|
|
832
|
+
this.setResultAndNotify({
|
|
833
|
+
success: false,
|
|
834
|
+
confidence: 0,
|
|
835
|
+
verifiedImage: primaryFrame || '',
|
|
836
|
+
error: err.message || 'Multi-backend verification failed',
|
|
837
|
+
metadata: {
|
|
838
|
+
timestamp: new Date().toISOString(),
|
|
839
|
+
backend: 'amazon',
|
|
840
|
+
confidence: 0,
|
|
841
|
+
attemptNumber: this.attemptCount
|
|
842
|
+
}
|
|
843
|
+
});
|
|
844
|
+
this.currentStep.set('result');
|
|
845
|
+
flowLog('processMulti', `Error handled, step set to: ${this.currentStep()}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
onDone(): void {
|
|
850
|
+
flowLog('action', 'onDone called (manual)');
|
|
851
|
+
const r = this.result();
|
|
852
|
+
if (r) {
|
|
853
|
+
flowLog('action', 'Emitting result from onDone');
|
|
854
|
+
this.resultSubject.next(r);
|
|
855
|
+
this.verificationComplete.emit(r);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
onRetry(): void {
|
|
860
|
+
flowLog('action', `onRetry called, attemptCount=${this.attemptCount}, maxRetries=${this.config?.maxRetries ?? 3}`);
|
|
861
|
+
if (this.attemptCount >= (this.config?.maxRetries ?? 3)) {
|
|
862
|
+
const r = this.result();
|
|
863
|
+
if (r) {
|
|
864
|
+
this.result.set({ ...r, error: 'Maximum retry attempts reached' });
|
|
865
|
+
this.emitResult(this.result()!);
|
|
866
|
+
}
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
this.processingProgress.set(0);
|
|
870
|
+
this.capturedFrames = [];
|
|
871
|
+
this.result.set(null);
|
|
872
|
+
|
|
873
|
+
if (this.useAwsFaceLiveness) {
|
|
874
|
+
flowLog('action', 'Retry → restarting AWS Face Liveness directly');
|
|
875
|
+
this.startAwsLivenessDirectly();
|
|
876
|
+
} else {
|
|
877
|
+
this.currentStep.set('preview');
|
|
878
|
+
flowLog('action', `Retry → step set to: ${this.currentStep()}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
onCancel(): void {
|
|
883
|
+
flowLog('action', 'onCancel called');
|
|
884
|
+
this.emitResult({
|
|
885
|
+
success: false,
|
|
886
|
+
confidence: 0,
|
|
887
|
+
verifiedImage: '',
|
|
888
|
+
error: 'User cancelled',
|
|
889
|
+
metadata: {
|
|
890
|
+
timestamp: new Date().toISOString(),
|
|
891
|
+
backend: 'amazon' as LivenessBackend,
|
|
892
|
+
confidence: 0,
|
|
893
|
+
attemptNumber: this.attemptCount
|
|
894
|
+
}
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
onAutoRetry(): void {
|
|
899
|
+
flowLog('action', 'Auto-retry triggered after countdown');
|
|
900
|
+
this.processingProgress.set(0);
|
|
901
|
+
this.capturedFrames = [];
|
|
902
|
+
this.result.set(null);
|
|
903
|
+
|
|
904
|
+
if (this.useAwsFaceLiveness) {
|
|
905
|
+
flowLog('action', 'Auto-retry → restarting AWS Face Liveness directly');
|
|
906
|
+
this.startAwsLivenessDirectly();
|
|
907
|
+
} else {
|
|
908
|
+
this.currentStep.set('preview');
|
|
909
|
+
flowLog('action', `Auto-retry → step set to: ${this.currentStep()}`);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
onAutoComplete(result: LivenessResult): void {
|
|
914
|
+
flowLog('action', 'Auto-complete triggered after countdown', { success: result.success, confidence: result.confidence });
|
|
915
|
+
this.resultSubject.next(result);
|
|
916
|
+
this.verificationComplete.emit(result);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Upload media to S3: prefers videoBlob, falls back to base64 image frame.
|
|
921
|
+
* Returns the S3 URL or undefined if upload fails/skipped.
|
|
922
|
+
*/
|
|
923
|
+
private async uploadMediaToS3(sessionId: string, primaryFrame: string): Promise<string | undefined> {
|
|
924
|
+
if (this.videoBlob) {
|
|
925
|
+
try {
|
|
926
|
+
flowLog('upload', '>>> Uploading video blob to S3...');
|
|
927
|
+
const res = await new Promise<{ videoUrl?: string }>((resolve, reject) => {
|
|
928
|
+
this.orchestrator.uploadVideoToS3(this.videoBlob!, sessionId).subscribe({ next: resolve, error: reject });
|
|
929
|
+
});
|
|
930
|
+
flowLog('upload', '<<< Video upload response:', { videoUrl: res.videoUrl });
|
|
931
|
+
return res.videoUrl;
|
|
932
|
+
} catch (err) {
|
|
933
|
+
flowWarn('upload', 'Video blob upload failed, trying image fallback', err);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (primaryFrame) {
|
|
938
|
+
try {
|
|
939
|
+
flowLog('upload', '>>> Uploading base64 image to S3...');
|
|
940
|
+
const res = await new Promise<{ videoUrl?: string }>((resolve, reject) => {
|
|
941
|
+
this.orchestrator.uploadBase64ImageToS3(primaryFrame, sessionId).subscribe({ next: resolve, error: reject });
|
|
942
|
+
});
|
|
943
|
+
flowLog('upload', '<<< Image upload response:', { videoUrl: res.videoUrl });
|
|
944
|
+
return res.videoUrl;
|
|
945
|
+
} catch (err) {
|
|
946
|
+
flowWarn('upload', 'Image upload failed (non-blocking)', err);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
return undefined;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private setResultAndNotify(r: LivenessResult): void {
|
|
954
|
+
flowLog('emit', 'Setting result (countdown will handle next action):', { success: r.success, error: r.error, confidence: r.confidence });
|
|
955
|
+
this.result.set(r);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private emitResult(r: LivenessResult): void {
|
|
959
|
+
flowLog('emit', 'Emitting result (closing flow):', { success: r.success, error: r.error, confidence: r.confidence });
|
|
960
|
+
this.resultSubject.next(r);
|
|
961
|
+
this.verificationComplete.emit(r);
|
|
962
|
+
}
|
|
963
|
+
}
|