@richard.fadiora/liveness-detection 4.3.8 → 4.3.10

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/README.md CHANGED
@@ -7,7 +7,7 @@ This package provides a **framework-agnostic core** with optimized wrappers for
7
7
 
8
8
  * * *
9
9
 
10
- ## 🚀 Key Features in v4.3.8
10
+ ## 🚀 Key Features in v4.3.10
11
11
 
12
12
 
13
13
 
@@ -127,15 +127,209 @@ function App() {
127
127
 
128
128
  | Angular Input | React Prop | Description |
129
129
  | --- | --- | --- |
130
- | containerClass | classNames.container | Main wrapper div |
131
- | videoClass | classNames.webcam | The video element |
132
- | buttonClass | classNames.button | The Start button |
133
- | timerClass | classNames.timer | Countdown timer text |
134
- | instructionBoxClass | classNames.challenge | Challenge instruction box |
135
- | statusOverlayClass | classNames.overlay | Overlay during verification |
136
- | errorMessageClass | classNames.error | Wrapper for error messages |
130
+ | `containerClass` | classNames.container | Main wrapper div |
131
+ | `videoClass` | classNames.webcam | The video element |
132
+ | `buttonClass` | classNames.button | The Start button |
133
+ | `timerClass` | classNames.timer | Countdown timer text |
134
+ | `instructionBoxClass` | classNames.challenge | Challenge instruction box |
135
+ | `statusOverlayClass` | classNames.overlay | Overlay during verification |
136
+ | `errorMessageClass` | classNames.error | Wrapper for error messages |
137
137
 
138
138
  * * *
139
+ ## Customizing Challenge Labels
140
+
141
+
142
+
143
+ You can override the default technical strings (e.g., `Thumbs_Up`) with user-friendly text or different languages using the `challengeMapping` input.
144
+
145
+ | Technical ID | Default Display |
146
+ | --- | --- |
147
+ | `Smile` | "Smile" |
148
+ | `Blink` | "Blink" |
149
+ | `Turn_Head` | "Turn Head" |
150
+ | `Thumbs_Up` | "Thumbs Up" |
151
+
152
+ **Example: Localization (French)**
153
+
154
+ TypeScript
155
+
156
+ // app.component.ts
157
+ challengeLabels = {
158
+ 'Smile': 'Souriez',
159
+ 'Blink': 'Clignez des yeux',
160
+ 'Turn_Head': 'Tournez la tête',
161
+ 'Thumbs_Up': 'Levez le pouce'
162
+ };
163
+ HTML
164
+
165
+ <LivenessCheck [challengeMapping]="challengeLabels"></LivenessCheck>
166
+
167
+ ###
168
+
169
+ ### 📥 Text & Label Customization
170
+
171
+ ###
172
+
173
+ Customize all user-facing strings to match your app's tone or language.
174
+
175
+ | Input Property | Default Value |
176
+ | --- | --- |
177
+ | `successMessage` | "Verification Successful!" |
178
+ | `errorMessage` | "Verification failed. Please try again." |
179
+ | `startButtonLabel` | "Start Verification" |
180
+ | `retryButtonLabel` | "Try Again" |
181
+
182
+ **Example Usage:**
183
+
184
+ HTML
185
+
186
+ <LivenessCheck
187
+ [successMessage]="'Identity Confirmed!'"
188
+ [errorMessage]="'We couldn\'t verify you. Move to a brighter room.'"
189
+ [startButtonLabel]="'Begin Scan'"
190
+ >
191
+ </LivenessCheck>
192
+
193
+ ## 🔄 State Synchronization
194
+
195
+ ###
196
+
197
+ The `onStateChange` event provides a real-time stream of the SDK's internal engine. This is useful for building custom instruction overlays or logging telemetry.
198
+
199
+ ### The `LivenessState` Object
200
+
201
+ ###
202
+
203
+ When the state changes, the SDK emits an object with the following structure:
204
+
205
+ | Property | Type | Description |
206
+ | --- | --- | --- |
207
+ | `status` | enum | The current phase of the SDK (see Status Flow below). |
208
+ | `sequence` | Challenge[] | The array of 3 randomized challenges (e.g., ['Smile', 'Blink', 'Turn_Head']). |
209
+ | `currentStep` | number | The index (0-2) of the challenge the user is currently performing. |
210
+ | `timeLeft` | number | The countdown timer in seconds before the session expires. |
211
+ | `isStepTransitioning` | boolean | true during the 1.5s pause after a successful challenge. |
212
+ | `errorMsg` | string | Contains the technical error code (e.g., SPOOF_BLUR_DETECTED) when status is 'error'. |
213
+
214
+ * * *
215
+
216
+ ### Implementation: Custom Error Mapping
217
+
218
+ ###
219
+
220
+ You can use the `onStateChange` handler to "intercept" technical error codes and map them to user-friendly instructions in your local UI.
221
+
222
+ **Example: Reactive Instructions**
223
+
224
+ TypeScript
225
+
226
+ // app.component.ts
227
+ handleStateUpdate(state: LivenessState) {
228
+ if (state.status === 'error' && state.errorMsg) {
229
+
230
+ // Pattern matching for specific error prefixes
231
+ switch (true) {
232
+ case state.errorMsg.startsWith('FACE_'):
233
+ this.myLocalStatus = "Center your face in the circle";
234
+ break;
235
+ case state.errorMsg.startsWith('LIGHT_'):
236
+ this.myLocalStatus = "Find a brighter spot";
237
+ break;
238
+ case state.errorMsg.startsWith('TIMEOUT'):
239
+ this.myLocalStatus = "Session expired. Tap retry.";
240
+ break;
241
+ default:
242
+ this.myLocalStatus = state.errorMsg;
243
+ }
244
+ }
245
+ }
246
+ HTML
247
+
248
+ <LivenessCheck
249
+ (onStateChange)="handleStateUpdate($event)"
250
+ [errorMessage]="myLocalStatus">
251
+ </LivenessCheck>
252
+
253
+ * * *
254
+
255
+ ### Why use the State Handler?
256
+
257
+ ###
258
+
259
+ * **Decoupled Logic:** You can keep the SDK's UI simple while handling complex business logic (like analytics or redirecting users) in your main app.
260
+
261
+ * **Dynamic UI:** Change your app's background color, show a progress bar, or trigger haptic feedback based on the `status` transitions.
262
+
263
+ * **Error Interception:** Translate technical backend codes into the specific "voice" of your brand.
264
+
265
+ ## 🚨 Error Reference Guide
266
+
267
+
268
+
269
+ When the SDK emits a `status: 'error'`, the `errorMsg` will contain one of the following technical codes. You can use these to provide contextual hints (e.g., "Too much light") in your UI.
270
+
271
+ | Error Code | Meaning | Recommended User Hint |
272
+ | --- | --- | --- |
273
+ | `SCREEN_REFLECTION_DETECTED` | Excessive glare or light bouncing off the screen. | "Move away from direct light or windows." |
274
+ | `SPOOF_BLUR_DETECTED` | Image is too dark or out of focus. | "Ensure your face is well-lit and clear." |
275
+ | `SCREEN_MOIRE_PATTERN_DETECTED` | Digital pixel patterns detected (indicates a photo/screen). | "Please use your physical device camera." |
276
+ | `STATIC_IMAGE_OR_SCREEN` | Natural micro-movements not detected. | "Blink or move slightly during the scan." |
277
+ | `NO_IMAGE_DATA` | Camera permissions denied or stream interrupted. | "Enable camera access in your settings." |
278
+ | `AI_SPOOF_DETECTION` | Low skin-texture confidence from the AI model. | "Verification failed. Please try again." |
279
+ | `Network Error` | The backend API could not be reached. | "Check your internet connection." |
280
+ | `Liveness Check Failed` | Generic fallback for unspecified validation errors. | "Please try again in a different environment." |
281
+
282
+ * * *
283
+
284
+ ### Pro-Tip: Implementing a User Hint System
285
+
286
+ ##
287
+
288
+ Instead of showing the raw code (which might confuse a non-technical user), use the `onStateChange` handler to map these codes to your friendly "Recommended User Hint" column.
289
+
290
+ TypeScript
291
+
292
+ // Example: Mapping technical codes to friendly messages
293
+ handleStateUpdate(state: LivenessState) {
294
+ const errorMap: Record<string, string> = {
295
+ 'SCREEN_REFLECTION_DETECTED': 'Too much light! Please move to a shaded area.',
296
+ 'SPOOF_BLUR_DETECTED': 'It’s a bit dark. Try increasing your screen brightness.',
297
+ 'NO_IMAGE_DATA': 'We can’t see you! Please check your camera permissions.'
298
+ };
299
+
300
+ if (state.status === 'error') {
301
+ this.friendlyMessage = errorMap[state.errorMsg] || "Something went wrong. Let's try again.";
302
+ }
303
+ }
304
+
305
+ * * *
306
+ ### 🧩 Type Handling
307
+
308
+ ##
309
+
310
+ The SDK emits state updates reactively. During the initialization phase, the state may briefly be `null` or `undefined`. Always use optional chaining when accessing state properties:
311
+
312
+ TypeScript
313
+
314
+ handleStateUpdate(state: LivenessState | null) {
315
+ if (state?.status === 'error') {
316
+ // safely handle error
317
+ }
318
+ }
319
+
320
+
321
+ ### 📦 Latest Changes Summary
322
+
323
+ ##
324
+
325
+ * **Zero-CSS Architecture:** All default styles removed; fully styleable via `[ngClass]` inputs.
326
+
327
+ * **Label Mapping:** Added `[challengeMapping]` to allow custom text for "Smile", "Blink", etc.
328
+
329
+ * **State Hook:** New `(onStateChange)` event for advanced lifecycle management.
330
+
331
+ * **Encapsulation:** Set to `ViewEncapsulation.None` for seamless Tailwind and Global CSS integration.
332
+
139
333
 
140
334
  ## ⚙️ Configuration & Thresholds
141
335
 
@@ -143,11 +337,11 @@ function App() {
143
337
 
144
338
  | Prop Name | Default | Description |
145
339
  | --- | --- | --- |
146
- | duration | 60 | Total session time in seconds |
147
- | smileThreshold | 0.20 | Minimum width for smile detection |
148
- | blinkThreshold | 0.01 | Sensitivity for eye-closure detection |
149
- | minturnHeadThreshold | 0.15 | Minimum yaw for right-turn detection |
150
- | maxturnHeadThreshold | 0.85 | Maximum yaw for left-turn detection |
340
+ | `duration` | 60 | Total session time in seconds |
341
+ | `smileThreshold` | 0.20 | Minimum width for smile detection |
342
+ | `blinkThreshold` | 0.01 | Sensitivity for eye-closure detection |
343
+ | `minturnHeadThreshold` | 0.15 | Minimum yaw for right-turn detection |
344
+ | `maxturnHeadThreshold` | 0.85 | Maximum yaw for left-turn detection |
151
345
 
152
346
  * * *
153
347
 
@@ -7,7 +7,7 @@ This package provides a **framework-agnostic core** with optimized wrappers for
7
7
 
8
8
  * * *
9
9
 
10
- ## 🚀 Key Features in v4.3.8
10
+ ## 🚀 Key Features in v4.3.10
11
11
 
12
12
 
13
13
 
@@ -127,15 +127,209 @@ function App() {
127
127
 
128
128
  | Angular Input | React Prop | Description |
129
129
  | --- | --- | --- |
130
- | containerClass | classNames.container | Main wrapper div |
131
- | videoClass | classNames.webcam | The video element |
132
- | buttonClass | classNames.button | The Start button |
133
- | timerClass | classNames.timer | Countdown timer text |
134
- | instructionBoxClass | classNames.challenge | Challenge instruction box |
135
- | statusOverlayClass | classNames.overlay | Overlay during verification |
136
- | errorMessageClass | classNames.error | Wrapper for error messages |
130
+ | `containerClass` | classNames.container | Main wrapper div |
131
+ | `videoClass` | classNames.webcam | The video element |
132
+ | `buttonClass` | classNames.button | The Start button |
133
+ | `timerClass` | classNames.timer | Countdown timer text |
134
+ | `instructionBoxClass` | classNames.challenge | Challenge instruction box |
135
+ | `statusOverlayClass` | classNames.overlay | Overlay during verification |
136
+ | `errorMessageClass` | classNames.error | Wrapper for error messages |
137
137
 
138
138
  * * *
139
+ ## Customizing Challenge Labels
140
+
141
+
142
+
143
+ You can override the default technical strings (e.g., `Thumbs_Up`) with user-friendly text or different languages using the `challengeMapping` input.
144
+
145
+ | Technical ID | Default Display |
146
+ | --- | --- |
147
+ | `Smile` | "Smile" |
148
+ | `Blink` | "Blink" |
149
+ | `Turn_Head` | "Turn Head" |
150
+ | `Thumbs_Up` | "Thumbs Up" |
151
+
152
+ **Example: Localization (French)**
153
+
154
+ TypeScript
155
+
156
+ // app.component.ts
157
+ challengeLabels = {
158
+ 'Smile': 'Souriez',
159
+ 'Blink': 'Clignez des yeux',
160
+ 'Turn_Head': 'Tournez la tête',
161
+ 'Thumbs_Up': 'Levez le pouce'
162
+ };
163
+ HTML
164
+
165
+ <LivenessCheck [challengeMapping]="challengeLabels"></LivenessCheck>
166
+
167
+ ###
168
+
169
+ ### 📥 Text & Label Customization
170
+
171
+ ###
172
+
173
+ Customize all user-facing strings to match your app's tone or language.
174
+
175
+ | Input Property | Default Value |
176
+ | --- | --- |
177
+ | `successMessage` | "Verification Successful!" |
178
+ | `errorMessage` | "Verification failed. Please try again." |
179
+ | `startButtonLabel` | "Start Verification" |
180
+ | `retryButtonLabel` | "Try Again" |
181
+
182
+ **Example Usage:**
183
+
184
+ HTML
185
+
186
+ <LivenessCheck
187
+ [successMessage]="'Identity Confirmed!'"
188
+ [errorMessage]="'We couldn\'t verify you. Move to a brighter room.'"
189
+ [startButtonLabel]="'Begin Scan'"
190
+ >
191
+ </LivenessCheck>
192
+
193
+ ## 🔄 State Synchronization
194
+
195
+ ###
196
+
197
+ The `onStateChange` event provides a real-time stream of the SDK's internal engine. This is useful for building custom instruction overlays or logging telemetry.
198
+
199
+ ### The `LivenessState` Object
200
+
201
+ ###
202
+
203
+ When the state changes, the SDK emits an object with the following structure:
204
+
205
+ | Property | Type | Description |
206
+ | --- | --- | --- |
207
+ | `status` | enum | The current phase of the SDK (see Status Flow below). |
208
+ | `sequence` | Challenge[] | The array of 3 randomized challenges (e.g., ['Smile', 'Blink', 'Turn_Head']). |
209
+ | `currentStep` | number | The index (0-2) of the challenge the user is currently performing. |
210
+ | `timeLeft` | number | The countdown timer in seconds before the session expires. |
211
+ | `isStepTransitioning` | boolean | true during the 1.5s pause after a successful challenge. |
212
+ | `errorMsg` | string | Contains the technical error code (e.g., SPOOF_BLUR_DETECTED) when status is 'error'. |
213
+
214
+ * * *
215
+
216
+ ### Implementation: Custom Error Mapping
217
+
218
+ ###
219
+
220
+ You can use the `onStateChange` handler to "intercept" technical error codes and map them to user-friendly instructions in your local UI.
221
+
222
+ **Example: Reactive Instructions**
223
+
224
+ TypeScript
225
+
226
+ // app.component.ts
227
+ handleStateUpdate(state: LivenessState) {
228
+ if (state.status === 'error' && state.errorMsg) {
229
+
230
+ // Pattern matching for specific error prefixes
231
+ switch (true) {
232
+ case state.errorMsg.startsWith('FACE_'):
233
+ this.myLocalStatus = "Center your face in the circle";
234
+ break;
235
+ case state.errorMsg.startsWith('LIGHT_'):
236
+ this.myLocalStatus = "Find a brighter spot";
237
+ break;
238
+ case state.errorMsg.startsWith('TIMEOUT'):
239
+ this.myLocalStatus = "Session expired. Tap retry.";
240
+ break;
241
+ default:
242
+ this.myLocalStatus = state.errorMsg;
243
+ }
244
+ }
245
+ }
246
+ HTML
247
+
248
+ <LivenessCheck
249
+ (onStateChange)="handleStateUpdate($event)"
250
+ [errorMessage]="myLocalStatus">
251
+ </LivenessCheck>
252
+
253
+ * * *
254
+
255
+ ### Why use the State Handler?
256
+
257
+ ###
258
+
259
+ * **Decoupled Logic:** You can keep the SDK's UI simple while handling complex business logic (like analytics or redirecting users) in your main app.
260
+
261
+ * **Dynamic UI:** Change your app's background color, show a progress bar, or trigger haptic feedback based on the `status` transitions.
262
+
263
+ * **Error Interception:** Translate technical backend codes into the specific "voice" of your brand.
264
+
265
+ ## 🚨 Error Reference Guide
266
+
267
+
268
+
269
+ When the SDK emits a `status: 'error'`, the `errorMsg` will contain one of the following technical codes. You can use these to provide contextual hints (e.g., "Too much light") in your UI.
270
+
271
+ | Error Code | Meaning | Recommended User Hint |
272
+ | --- | --- | --- |
273
+ | `SCREEN_REFLECTION_DETECTED` | Excessive glare or light bouncing off the screen. | "Move away from direct light or windows." |
274
+ | `SPOOF_BLUR_DETECTED` | Image is too dark or out of focus. | "Ensure your face is well-lit and clear." |
275
+ | `SCREEN_MOIRE_PATTERN_DETECTED` | Digital pixel patterns detected (indicates a photo/screen). | "Please use your physical device camera." |
276
+ | `STATIC_IMAGE_OR_SCREEN` | Natural micro-movements not detected. | "Blink or move slightly during the scan." |
277
+ | `NO_IMAGE_DATA` | Camera permissions denied or stream interrupted. | "Enable camera access in your settings." |
278
+ | `AI_SPOOF_DETECTION` | Low skin-texture confidence from the AI model. | "Verification failed. Please try again." |
279
+ | `Network Error` | The backend API could not be reached. | "Check your internet connection." |
280
+ | `Liveness Check Failed` | Generic fallback for unspecified validation errors. | "Please try again in a different environment." |
281
+
282
+ * * *
283
+
284
+ ### Pro-Tip: Implementing a User Hint System
285
+
286
+ ##
287
+
288
+ Instead of showing the raw code (which might confuse a non-technical user), use the `onStateChange` handler to map these codes to your friendly "Recommended User Hint" column.
289
+
290
+ TypeScript
291
+
292
+ // Example: Mapping technical codes to friendly messages
293
+ handleStateUpdate(state: LivenessState) {
294
+ const errorMap: Record<string, string> = {
295
+ 'SCREEN_REFLECTION_DETECTED': 'Too much light! Please move to a shaded area.',
296
+ 'SPOOF_BLUR_DETECTED': 'It’s a bit dark. Try increasing your screen brightness.',
297
+ 'NO_IMAGE_DATA': 'We can’t see you! Please check your camera permissions.'
298
+ };
299
+
300
+ if (state.status === 'error') {
301
+ this.friendlyMessage = errorMap[state.errorMsg] || "Something went wrong. Let's try again.";
302
+ }
303
+ }
304
+
305
+ * * *
306
+ ### 🧩 Type Handling
307
+
308
+ ##
309
+
310
+ The SDK emits state updates reactively. During the initialization phase, the state may briefly be `null` or `undefined`. Always use optional chaining when accessing state properties:
311
+
312
+ TypeScript
313
+
314
+ handleStateUpdate(state: LivenessState | null) {
315
+ if (state?.status === 'error') {
316
+ // safely handle error
317
+ }
318
+ }
319
+
320
+
321
+ ### 📦 Latest Changes Summary
322
+
323
+ ##
324
+
325
+ * **Zero-CSS Architecture:** All default styles removed; fully styleable via `[ngClass]` inputs.
326
+
327
+ * **Label Mapping:** Added `[challengeMapping]` to allow custom text for "Smile", "Blink", etc.
328
+
329
+ * **State Hook:** New `(onStateChange)` event for advanced lifecycle management.
330
+
331
+ * **Encapsulation:** Set to `ViewEncapsulation.None` for seamless Tailwind and Global CSS integration.
332
+
139
333
 
140
334
  ## ⚙️ Configuration & Thresholds
141
335
 
@@ -143,11 +337,11 @@ function App() {
143
337
 
144
338
  | Prop Name | Default | Description |
145
339
  | --- | --- | --- |
146
- | duration | 60 | Total session time in seconds |
147
- | smileThreshold | 0.20 | Minimum width for smile detection |
148
- | blinkThreshold | 0.01 | Sensitivity for eye-closure detection |
149
- | minturnHeadThreshold | 0.15 | Minimum yaw for right-turn detection |
150
- | maxturnHeadThreshold | 0.85 | Maximum yaw for left-turn detection |
340
+ | `duration` | 60 | Total session time in seconds |
341
+ | `smileThreshold` | 0.20 | Minimum width for smile detection |
342
+ | `blinkThreshold` | 0.01 | Sensitivity for eye-closure detection |
343
+ | `minturnHeadThreshold` | 0.15 | Minimum yaw for right-turn detection |
344
+ | `maxturnHeadThreshold` | 0.85 | Maximum yaw for left-turn detection |
151
345
 
152
346
  * * *
153
347
 
@@ -308,6 +308,7 @@ class LivenessComponent {
308
308
  videoWrapperClass = '';
309
309
  videoClass = '';
310
310
  timerClass = '';
311
+ stateClass = '';
311
312
  challengeTextClass = '';
312
313
  buttonClass = '';
313
314
  retryButtonClass = '';
@@ -315,8 +316,20 @@ class LivenessComponent {
315
316
  successMessageClass = '';
316
317
  errorMessageClass = '';
317
318
  instructionBoxClass = '';
319
+ // --- Content Configuration ---
320
+ successMessage = 'Verification Successful!';
321
+ errorMessage = 'Verification failed. Please try again.';
322
+ retryButtonLabel = 'Try Again';
323
+ startButtonLabel = 'Start Verification';
324
+ challengeMapping = {
325
+ 'Smile': 'Smile',
326
+ 'Blink': 'Blink',
327
+ 'Turn_Head': 'Turn Head',
328
+ 'Thumbs_Up': 'Thumbs Up' // Default with a space
329
+ };
318
330
  onComplete = new EventEmitter();
319
331
  onError = new EventEmitter();
332
+ onStateChange = new EventEmitter();
320
333
  constructor(liveness) {
321
334
  this.liveness = liveness;
322
335
  }
@@ -332,6 +345,10 @@ class LivenessComponent {
332
345
  onComplete: (res) => this.onComplete.emit(res),
333
346
  onError: (err) => this.onError.emit(err)
334
347
  });
348
+ this.liveness.state$.subscribe(state => {
349
+ // This bridges the Service to the Component's @Output
350
+ this.onStateChange.emit(state);
351
+ });
335
352
  }
336
353
  async ngAfterViewInit() {
337
354
  try {
@@ -354,11 +371,11 @@ class LivenessComponent {
354
371
  stream?.getTracks().forEach(track => track.stop());
355
372
  }
356
373
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: LivenessComponent, deps: [{ token: LivenessService }], target: i0.ɵɵFactoryTarget.Component });
357
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.3", type: LivenessComponent, isStandalone: true, selector: "LivenessCheck", inputs: { apiUrl: "apiUrl", duration: "duration", smileThreshold: "smileThreshold", blinkThreshold: "blinkThreshold", minturnHeadThreshold: "minturnHeadThreshold", maxturnHeadThreshold: "maxturnHeadThreshold", containerClass: "containerClass", videoWrapperClass: "videoWrapperClass", videoClass: "videoClass", timerClass: "timerClass", challengeTextClass: "challengeTextClass", buttonClass: "buttonClass", retryButtonClass: "retryButtonClass", statusOverlayClass: "statusOverlayClass", successMessageClass: "successMessageClass", errorMessageClass: "errorMessageClass", instructionBoxClass: "instructionBoxClass" }, outputs: { onComplete: "onComplete", onError: "onError" }, viewQueries: [{ propertyName: "webcamRef", first: true, predicate: ["webcam"], descendants: true }], ngImport: i0, template: "<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n Challenge: {{ state.sequence[state.currentStep] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n Verification Successful!\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ state.errorMsg || 'Verification failed. Please try again.' }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}s\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n Start Verification\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n Try Again\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.UpperCasePipe, name: "uppercase" }], encapsulation: i0.ViewEncapsulation.None });
374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.3", type: LivenessComponent, isStandalone: true, selector: "LivenessCheck", inputs: { apiUrl: "apiUrl", duration: "duration", smileThreshold: "smileThreshold", blinkThreshold: "blinkThreshold", minturnHeadThreshold: "minturnHeadThreshold", maxturnHeadThreshold: "maxturnHeadThreshold", containerClass: "containerClass", videoWrapperClass: "videoWrapperClass", videoClass: "videoClass", timerClass: "timerClass", stateClass: "stateClass", challengeTextClass: "challengeTextClass", buttonClass: "buttonClass", retryButtonClass: "retryButtonClass", statusOverlayClass: "statusOverlayClass", successMessageClass: "successMessageClass", errorMessageClass: "errorMessageClass", instructionBoxClass: "instructionBoxClass", successMessage: "successMessage", errorMessage: "errorMessage", retryButtonLabel: "retryButtonLabel", startButtonLabel: "startButtonLabel", challengeMapping: "challengeMapping" }, outputs: { onComplete: "onComplete", onError: "onError", onStateChange: "onStateChange" }, viewQueries: [{ propertyName: "webcamRef", first: true, predicate: ["webcam"], descendants: true }], ngImport: i0, template: "<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\" [ngClass] = \"stateClass\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n {{ challengeMapping[state.sequence[state.currentStep]] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n {{ successMessage }}\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n {{ startButtonLabel }}\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n {{ retryButtonLabel }}\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }, { kind: "pipe", type: i2.UpperCasePipe, name: "uppercase" }], encapsulation: i0.ViewEncapsulation.None });
358
375
  }
359
376
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: LivenessComponent, decorators: [{
360
377
  type: Component,
361
- args: [{ selector: 'LivenessCheck', standalone: true, imports: [CommonModule], encapsulation: ViewEncapsulation.None, template: "<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n Challenge: {{ state.sequence[state.currentStep] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n Verification Successful!\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ state.errorMsg || 'Verification failed. Please try again.' }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}s\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n Start Verification\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n Try Again\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>" }]
378
+ args: [{ selector: 'LivenessCheck', standalone: true, imports: [CommonModule], encapsulation: ViewEncapsulation.None, template: "<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\" [ngClass] = \"stateClass\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n {{ challengeMapping[state.sequence[state.currentStep]] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n {{ successMessage }}\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n {{ startButtonLabel }}\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n {{ retryButtonLabel }}\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>" }]
362
379
  }], ctorParameters: () => [{ type: LivenessService }], propDecorators: { webcamRef: [{
363
380
  type: ViewChild,
364
381
  args: ['webcam']
@@ -382,6 +399,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
382
399
  type: Input
383
400
  }], timerClass: [{
384
401
  type: Input
402
+ }], stateClass: [{
403
+ type: Input
385
404
  }], challengeTextClass: [{
386
405
  type: Input
387
406
  }], buttonClass: [{
@@ -396,10 +415,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImpor
396
415
  type: Input
397
416
  }], instructionBoxClass: [{
398
417
  type: Input
418
+ }], successMessage: [{
419
+ type: Input
420
+ }], errorMessage: [{
421
+ type: Input
422
+ }], retryButtonLabel: [{
423
+ type: Input
424
+ }], startButtonLabel: [{
425
+ type: Input
426
+ }], challengeMapping: [{
427
+ type: Input
399
428
  }], onComplete: [{
400
429
  type: Output
401
430
  }], onError: [{
402
431
  type: Output
432
+ }], onStateChange: [{
433
+ type: Output
403
434
  }] } });
404
435
 
405
436
  // src/api.ts
@@ -1 +1 @@
1
- {"version":3,"file":"richard.fadiora-liveness-detection.mjs","sources":["../../../src/core/LivenessEngine.ts","../../../src/angular/liveness.service.ts","../../../src/angular/liveness.component.ts","../../../src/angular/liveness.component.html","../../../src/api.ts","../../../src/richard.fadiora-liveness-detection.ts"],"sourcesContent":["// src/core/LivenessEngine.ts\r\nimport { FaceLandmarker, HandLandmarker, FilesetResolver, NormalizedLandmark } from \"@mediapipe/tasks-vision\";\r\n\r\nexport type Challenge = \"Smile\" | \"Blink\" | \"Turn_Head\" | \"Thumbs_Up\";\r\n\r\nexport interface LivenessSDKResult {\r\n success: boolean;\r\n image?: string;\r\n reason?: string;\r\n skinConfidence?: number;\r\n}\r\n\r\nexport interface LivenessState {\r\n status: \"loading\" | \"ready\" | \"capturing\" | \"verifying\" | \"success\" | \"error\" | \"expired\";\r\n sequence: Challenge[];\r\n currentStep: number;\r\n timeLeft: number;\r\n isStepTransitioning: boolean;\r\n errorMsg: string;\r\n}\r\n\r\nexport interface LivenessEngineConfig {\r\n apiUrl: string;\r\n duration?: number;\r\n smileThreshold?: number;\r\n blinkThreshold?: number;\r\n minturnHeadThreshold?: number;\r\n maxturnHeadThreshold?: number;\r\n onStateChange?: (state: LivenessState) => void;\r\n onComplete?: (result: LivenessSDKResult) => void;\r\n onError?: (error: LivenessSDKResult) => void;\r\n}\r\n\r\nexport class LivenessEngine {\r\n private config: Required<LivenessEngineConfig>;\r\n private models: { face: FaceLandmarker | null; hand: HandLandmarker | null } = { face: null, hand: null };\r\n private webcam: HTMLVideoElement | null = null;\r\n private state: LivenessState;\r\n \r\n private currentStepRef = 0;\r\n private isStepTransitioningRef = false;\r\n private timerId: ReturnType<typeof setInterval> | null = null;\r\n private requestId: number | null = null;\r\n \r\n private offscreenCanvas = document.createElement(\"canvas\");\r\n private CHALLENGE_POOL: Challenge[] = [\"Smile\", \"Blink\", \"Turn_Head\", \"Thumbs_Up\"];\r\n\r\n constructor(config: LivenessEngineConfig) {\r\n this.config = {\r\n apiUrl: config.apiUrl,\r\n duration: config.duration ?? 60,\r\n smileThreshold: config.smileThreshold ?? 0.2,\r\n blinkThreshold: config.blinkThreshold ?? 0.012,\r\n minturnHeadThreshold: config.minturnHeadThreshold ?? 0.15,\r\n maxturnHeadThreshold: config.maxturnHeadThreshold ?? 0.85,\r\n onStateChange: config.onStateChange || (() => {}),\r\n onComplete: config.onComplete || (() => {}),\r\n onError: config.onError || (() => {}),\r\n };\r\n\r\n this.state = {\r\n status: \"loading\",\r\n sequence: [],\r\n currentStep: 0,\r\n timeLeft: this.config.duration,\r\n isStepTransitioning: false,\r\n errorMsg: \"\",\r\n };\r\n \r\n this.offscreenCanvas.width = 224;\r\n this.offscreenCanvas.height = 224;\r\n }\r\n\r\n /**\r\n * Phase 1: Load AI Assets (Call this immediately on mount)\r\n */\r\n public async loadModels() {\r\n console.log(\"[LivenessEngine] Loading AI Models...\");\r\n try {\r\n const vision = await FilesetResolver.forVisionTasks(\r\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm\"\r\n );\r\n\r\n this.models.face = await FaceLandmarker.createFromOptions(vision, {\r\n baseOptions: {\r\n modelAssetPath: \"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task\",\r\n delegate: \"GPU\",\r\n },\r\n outputFaceBlendshapes: true,\r\n runningMode: \"VIDEO\",\r\n });\r\n\r\n this.models.hand = await HandLandmarker.createFromOptions(vision, {\r\n baseOptions: {\r\n modelAssetPath: \"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task\",\r\n delegate: \"GPU\",\r\n },\r\n runningMode: \"VIDEO\",\r\n numHands: 1,\r\n });\r\n\r\n this.generateSequence();\r\n // Stay in 'loading' or move to a 'ready_to_mount' status if you prefer\r\n // For now, we move to ready to signal the UI can show the camera\r\n this.updateState({ status: \"ready\" });\r\n console.log(\"[LivenessEngine] Models Loaded.\");\r\n } catch (err) {\r\n console.error(\"[LivenessEngine] Init Error:\", err);\r\n this.updateState({ status: \"error\", errorMsg: \"Failed to load security assets.\" });\r\n }\r\n }\r\n\r\n /**\r\n * Phase 2: Attach Video (Call this once the Webcam is in the DOM)\r\n */\r\n public attachVideo = (video: HTMLVideoElement) => {\r\n this.webcam = video;\r\n console.log(\"[LivenessEngine] Video stream attached.\");\r\n };\r\n\r\n public start = () => {\r\n if (this.state.status !== \"ready\" || !this.webcam) {\r\n console.warn(\"[LivenessEngine] Engine not ready or video missing.\");\r\n return;\r\n }\r\n\r\n console.log(\"[LivenessEngine] Session Starting...\");\r\n this.state.status = \"capturing\"; // Sync internal status immediately\r\n this.updateState({ status: \"capturing\" });\r\n this.startTimer();\r\n this.detectLoop();\r\n };\r\n\r\n public stop = () => {\r\n if (this.timerId) clearInterval(this.timerId);\r\n if (this.requestId) cancelAnimationFrame(this.requestId);\r\n this.timerId = null;\r\n this.requestId = null;\r\n };\r\n\r\n public reset = () => {\r\n this.stop();\r\n this.currentStepRef = 0;\r\n this.isStepTransitioningRef = false;\r\n this.generateSequence();\r\n this.updateState({\r\n status: \"ready\",\r\n currentStep: 0,\r\n timeLeft: this.config.duration,\r\n isStepTransitioning: false,\r\n errorMsg: \"\",\r\n });\r\n };\r\n\r\n private updateState(newState: Partial<LivenessState>) {\r\n this.state = { ...this.state, ...newState };\r\n this.config.onStateChange(this.state);\r\n }\r\n\r\n private generateSequence(): Challenge[] {\r\n const seq = [...this.CHALLENGE_POOL].sort(() => 0.5 - Math.random()).slice(0, 3);\r\n this.state.sequence = seq;\r\n return seq;\r\n }\r\n\r\n private startTimer() {\r\n if (this.timerId) clearInterval(this.timerId);\r\n this.timerId = setInterval(() => {\r\n if (this.state.timeLeft > 0) {\r\n this.updateState({ timeLeft: this.state.timeLeft - 1 });\r\n } else {\r\n this.stop();\r\n this.updateState({ status: \"expired\" });\r\n }\r\n }, 1000);\r\n }\r\n\r\n private detectLoop = () => {\r\n if (this.state.status !== \"capturing\" || !this.webcam || !this.models.face) return;\r\n\r\n const now = performance.now();\r\n try {\r\n const faceRes = this.models.face.detectForVideo(this.webcam, now);\r\n const handRes = this.models.hand?.detectForVideo(this.webcam, now);\r\n\r\n const currentChallenge = this.state.sequence[this.currentStepRef];\r\n\r\n if (this.checkAction(faceRes, handRes, currentChallenge)) {\r\n this.handleStepSuccess();\r\n return; // handleStepSuccess schedules the next frame after a delay\r\n }\r\n } catch (err) {\r\n console.error(\"[LivenessEngine] Loop detection error:\", err);\r\n }\r\n\r\n this.requestId = requestAnimationFrame(this.detectLoop);\r\n };\r\n\r\n private handleStepSuccess() {\r\n if (this.isStepTransitioningRef) return;\r\n \r\n this.isStepTransitioningRef = true;\r\n this.updateState({ isStepTransitioning: true });\r\n\r\n setTimeout(() => {\r\n if (this.currentStepRef < this.state.sequence.length - 1) {\r\n this.currentStepRef++;\r\n this.isStepTransitioningRef = false;\r\n this.updateState({\r\n currentStep: this.currentStepRef,\r\n isStepTransitioning: false,\r\n });\r\n this.requestId = requestAnimationFrame(this.detectLoop);\r\n } else {\r\n this.sendFinalProof();\r\n }\r\n }, 1500);\r\n }\r\n\r\n private checkAction(faceRes: any, handRes: any, challenge: Challenge): boolean {\r\n if (this.isStepTransitioningRef) return false;\r\n\r\n if (challenge === \"Thumbs_Up\" && handRes?.landmarks?.length > 0) {\r\n const l = handRes.landmarks[0];\r\n return l[4].y < l[2].y && [8, 12, 16, 20].every(i => l[i].y > l[i - 2].y);\r\n }\r\n\r\n if (faceRes?.faceLandmarks?.length > 0) {\r\n const lms: NormalizedLandmark[] = faceRes.faceLandmarks[0];\r\n switch (challenge) {\r\n case \"Smile\":\r\n return (lms[291].x - lms[61].x) > this.config.smileThreshold;\r\n case \"Blink\":\r\n const leftEar = Math.abs(lms[159].y - lms[145].y);\r\n const rightEar = Math.abs(lms[386].y - lms[374].y);\r\n return ((leftEar + rightEar) / 2) < this.config.blinkThreshold;\r\n case \"Turn_Head\":\r\n const yaw = (lms[1].x - lms[33].x) / (lms[263].x - lms[33].x);\r\n return yaw < this.config.minturnHeadThreshold || yaw > this.config.maxturnHeadThreshold;\r\n default: return false;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private async sendFinalProof() {\r\n this.stop();\r\n this.updateState({ status: \"verifying\" });\r\n\r\n try {\r\n const blobs: Blob[] = [];\r\n let finalBase64 = \"\";\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (!this.webcam) break;\r\n const faceRes = this.models.face?.detectForVideo(this.webcam, performance.now());\r\n if (faceRes?.faceLandmarks?.[0]) {\r\n const canvas = this.getFaceCrop(this.webcam, faceRes.faceLandmarks[0]);\r\n const blob = await new Promise<Blob | null>(res => canvas.toBlob(res, \"image/jpeg\", 0.9));\r\n if (blob) blobs.push(blob);\r\n if (i === 4) finalBase64 = canvas.toDataURL(\"image/jpeg\");\r\n }\r\n await new Promise(r => setTimeout(r, 100));\r\n }\r\n\r\n const { verifyLiveness } = await import(\"../api\");\r\n const result = await verifyLiveness(this.config.apiUrl, blobs, this.state.sequence);\r\n\r\n if (result.is_live) {\r\n this.updateState({ status: \"success\" });\r\n this.config.onComplete({ success: true, image: finalBase64, skinConfidence: result.skin_confidence });\r\n } else {\r\n const reason = result.reason || \"Liveness check failed\";\r\n this.updateState({ status: \"error\", errorMsg: reason });\r\n this.config.onError({ success: false, reason });\r\n }\r\n } catch (err) {\r\n this.updateState({ status: \"error\", errorMsg: \"Verification failed.\" });\r\n this.config.onError({ success: false, reason: \"Network error\" });\r\n }\r\n }\r\n\r\n private getFaceCrop(video: HTMLVideoElement, landmarks: NormalizedLandmark[]): HTMLCanvasElement {\r\n const ctx = this.offscreenCanvas.getContext(\"2d\")!;\r\n const xs = landmarks.map(l => l.x * video.videoWidth);\r\n const ys = landmarks.map(l => l.y * video.videoHeight);\r\n \r\n const minX = Math.min(...xs);\r\n const maxX = Math.max(...xs);\r\n const minY = Math.min(...ys);\r\n const maxY = Math.max(...ys);\r\n \r\n const width = maxX - minX;\r\n const height = maxY - minY;\r\n const margin = width * 0.3;\r\n\r\n ctx.clearRect(0, 0, 224, 224);\r\n ctx.drawImage(\r\n video,\r\n minX - margin, minY - margin, \r\n width + margin * 2, height + margin * 2,\r\n 0, 0, 224, 224\r\n );\r\n\r\n return this.offscreenCanvas;\r\n }\r\n}","import { Injectable, NgZone, OnDestroy } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\nimport { LivenessEngine, LivenessState, LivenessEngineConfig } from '../core/LivenessEngine';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class LivenessService implements OnDestroy {\r\n private engine!: LivenessEngine;\r\n private stateSubject = new BehaviorSubject<LivenessState | null>(null);\r\n \r\n // Components will subscribe to this\r\n public state$: Observable<LivenessState | null> = this.stateSubject.asObservable();\r\n\r\n constructor(private ngZone: NgZone) {}\r\n\r\n public init(config: LivenessEngineConfig) {\r\n // Run inside runOutsideAngular to prevent 60fps change detection triggers\r\n this.ngZone.runOutsideAngular(() => {\r\n this.engine = new LivenessEngine({\r\n ...config,\r\n onStateChange: (state) => {\r\n // Bring state changes BACK into the zone so the UI updates\r\n this.ngZone.run(() => this.stateSubject.next(state));\r\n }\r\n });\r\n this.engine.loadModels();\r\n });\r\n }\r\n\r\n public attach(video: HTMLVideoElement) {\r\n this.engine?.attachVideo(video);\r\n }\r\n\r\n public start() {\r\n this.engine?.start();\r\n }\r\n\r\n public reset() {\r\n this.engine?.reset();\r\n }\r\n\r\n ngOnDestroy() {\r\n this.engine?.stop();\r\n }\r\n}","import { Component, ElementRef, ViewChild, AfterViewInit, Input, Output, EventEmitter, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';\r\nimport { LivenessService } from './liveness.service';\r\nimport { LivenessSDKResult, Challenge } from '../core/LivenessEngine';\r\nimport { CommonModule } from '@angular/common'\r\n\r\n@Component({\r\n selector: 'LivenessCheck',\r\n standalone: true, // Make it standalone\r\n imports: [CommonModule], // Add CommonModule here to fix NG8002\r\n templateUrl: './liveness.component.html',\r\n encapsulation: ViewEncapsulation.None,\r\n})\r\n\r\nexport class LivenessComponent implements OnInit, AfterViewInit, OnDestroy {\r\n @ViewChild('webcam') webcamRef!: ElementRef<HTMLVideoElement>;\r\n\r\n // --- Logic Configuration ---\r\n @Input() apiUrl!: string;\r\n @Input() duration = 60;\r\n @Input() smileThreshold?: number;\r\n @Input() blinkThreshold?: number;\r\n @Input() minturnHeadThreshold?: number;\r\n @Input() maxturnHeadThreshold?: number;\r\n\r\n // --- Styling Configuration (Class Names) ---\r\n @Input() containerClass = '';\r\n @Input() videoWrapperClass = '';\r\n @Input() videoClass = '';\r\n @Input() timerClass = '';\r\n @Input() challengeTextClass = '';\r\n @Input() buttonClass = '';\r\n @Input() retryButtonClass = '';\r\n @Input() statusOverlayClass = '';\r\n @Input() successMessageClass = '';\r\n @Input() errorMessageClass = '';\r\n @Input() instructionBoxClass = '';\r\n\r\n @Output() onComplete = new EventEmitter<LivenessSDKResult>();\r\n @Output() onError = new EventEmitter<any>();\r\n\r\n constructor(public liveness: LivenessService) {}\r\n\r\n ngOnInit() {\r\n // Pass the custom thresholds directly to the service\r\n this.liveness.init({\r\n apiUrl: this.apiUrl,\r\n duration: this.duration,\r\n smileThreshold: this.smileThreshold,\r\n blinkThreshold: this.blinkThreshold,\r\n minturnHeadThreshold: this.minturnHeadThreshold,\r\n maxturnHeadThreshold: this.maxturnHeadThreshold,\r\n onComplete: (res) => this.onComplete.emit(res),\r\n onError: (err) => this.onError.emit(err)\r\n });\r\n }\r\n\r\n async ngAfterViewInit() {\r\n try {\r\n const stream = await navigator.mediaDevices.getUserMedia({ \r\n video: { width: 640, height: 480, facingMode: 'user' } \r\n });\r\n const video = this.webcamRef.nativeElement;\r\n video.srcObject = stream;\r\n video.onloadedmetadata = () => {\r\n video.play();\r\n this.liveness.attach(video);\r\n };\r\n } catch (e) {\r\n this.onError.emit({ success: false, reason: 'Camera access denied' });\r\n }\r\n }\r\n\r\n ngOnDestroy() {\r\n const stream = this.webcamRef?.nativeElement?.srcObject as MediaStream;\r\n stream?.getTracks().forEach(track => track.stop());\r\n }\r\n}","<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n Challenge: {{ state.sequence[state.currentStep] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n Verification Successful!\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ state.errorMsg || 'Verification failed. Please try again.' }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}s\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n Start Verification\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n Try Again\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>","// src/api.ts\r\n\r\nimport { Challenge } from \"./core/LivenessEngine\";\r\n\r\nexport interface LivenessResponse {\r\n is_live: boolean;\r\n reason?: string;\r\n skin_confidence?: number;\r\n [key: string]: any; // allow extra fields if backend returns more\r\n}\r\n\r\n/**\r\n * Sends captured frames to the backend API for liveness verification.\r\n * @param apiUrl - Base URL of the liveness backend\r\n * @param frameBlobs - Array of Blob objects representing captured frames\r\n * @param challenge - The current challenge string\r\n * @returns LivenessResponse from backend\r\n */\r\nexport const verifyLiveness = async (\r\n apiUrl: string,\r\n frameBlobs: Blob[],\r\n challenge: Challenge[]\r\n): Promise<LivenessResponse> => {\r\n const formData = new FormData();\r\n\r\n frameBlobs.forEach((blob, i) => {\r\n formData.append('files', blob, `frame_${i}.jpg`);\r\n });\r\n formData.append('challenge', JSON.stringify(challenge));\r\n\r\n const response = await fetch(`${apiUrl}/v1/verify`, {\r\n method: 'POST',\r\n body: formData,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(\"Network response was not ok\");\r\n }\r\n\r\n const data: LivenessResponse = await response.json();\r\n return data;\r\n};","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.LivenessService"],"mappings":";;;;;;;AAAA;MAiCa,cAAc,CAAA;AACjB,IAAA,MAAM;IACN,MAAM,GAAiE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE;IACjG,MAAM,GAA4B,IAAI;AACtC,IAAA,KAAK;IAEL,cAAc,GAAG,CAAC;IAClB,sBAAsB,GAAG,KAAK;IAC9B,OAAO,GAA0C,IAAI;IACrD,SAAS,GAAkB,IAAI;AAE/B,IAAA,eAAe,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC;IAClD,cAAc,GAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,CAAC;AAElF,IAAA,WAAA,CAAY,MAA4B,EAAA;QACtC,IAAI,CAAC,MAAM,GAAG;YACZ,MAAM,EAAE,MAAM,CAAC,MAAM;AACrB,YAAA,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,EAAE;AAC/B,YAAA,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,GAAG;AAC5C,YAAA,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,KAAK;AAC9C,YAAA,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,IAAI;AACzD,YAAA,oBAAoB,EAAE,MAAM,CAAC,oBAAoB,IAAI,IAAI;YACzD,aAAa,EAAE,MAAM,CAAC,aAAa,KAAK,MAAK,EAAE,CAAC,CAAC;YACjD,UAAU,EAAE,MAAM,CAAC,UAAU,KAAK,MAAK,EAAE,CAAC,CAAC;YAC3C,OAAO,EAAE,MAAM,CAAC,OAAO,KAAK,MAAK,EAAE,CAAC,CAAC;SACtC;QAED,IAAI,CAAC,KAAK,GAAG;AACX,YAAA,MAAM,EAAE,SAAS;AACjB,YAAA,QAAQ,EAAE,EAAE;AACZ,YAAA,WAAW,EAAE,CAAC;AACd,YAAA,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;AAC9B,YAAA,mBAAmB,EAAE,KAAK;AAC1B,YAAA,QAAQ,EAAE,EAAE;SACb;AAED,QAAA,IAAI,CAAC,eAAe,CAAC,KAAK,GAAG,GAAG;AAChC,QAAA,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,GAAG;IACnC;AAEA;;AAEG;AACI,IAAA,MAAM,UAAU,GAAA;AACrB,QAAA,OAAO,CAAC,GAAG,CAAC,uCAAuC,CAAC;AACpD,QAAA,IAAI;YACF,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,cAAc,CACjD,kEAAkE,CACnE;YAED,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,MAAM,cAAc,CAAC,iBAAiB,CAAC,MAAM,EAAE;AAChE,gBAAA,WAAW,EAAE;AACX,oBAAA,cAAc,EAAE,gHAAgH;AAChI,oBAAA,QAAQ,EAAE,KAAK;AAChB,iBAAA;AACD,gBAAA,qBAAqB,EAAE,IAAI;AAC3B,gBAAA,WAAW,EAAE,OAAO;AACrB,aAAA,CAAC;YAEF,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,MAAM,cAAc,CAAC,iBAAiB,CAAC,MAAM,EAAE;AAChE,gBAAA,WAAW,EAAE;AACX,oBAAA,cAAc,EAAE,gHAAgH;AAChI,oBAAA,QAAQ,EAAE,KAAK;AAChB,iBAAA;AACD,gBAAA,WAAW,EAAE,OAAO;AACpB,gBAAA,QAAQ,EAAE,CAAC;AACZ,aAAA,CAAC;YAEF,IAAI,CAAC,gBAAgB,EAAE;;;YAGvB,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACrC,YAAA,OAAO,CAAC,GAAG,CAAC,iCAAiC,CAAC;QAChD;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,OAAO,CAAC,KAAK,CAAC,8BAA8B,EAAE,GAAG,CAAC;AAClD,YAAA,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,iCAAiC,EAAE,CAAC;QACpF;IACF;AAEA;;AAEG;AACI,IAAA,WAAW,GAAG,CAAC,KAAuB,KAAI;AAC/C,QAAA,IAAI,CAAC,MAAM,GAAG,KAAK;AACnB,QAAA,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC;AACxD,IAAA,CAAC;IAEM,KAAK,GAAG,MAAK;AAClB,QAAA,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AACjD,YAAA,OAAO,CAAC,IAAI,CAAC,qDAAqD,CAAC;YACnE;QACF;AAEA,QAAA,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC;QACnD,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,WAAW,CAAC;QAChC,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,UAAU,EAAE;QACjB,IAAI,CAAC,UAAU,EAAE;AACnB,IAAA,CAAC;IAEM,IAAI,GAAG,MAAK;QACjB,IAAI,IAAI,CAAC,OAAO;AAAE,YAAA,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;QAC7C,IAAI,IAAI,CAAC,SAAS;AAAE,YAAA,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC;AACxD,QAAA,IAAI,CAAC,OAAO,GAAG,IAAI;AACnB,QAAA,IAAI,CAAC,SAAS,GAAG,IAAI;AACvB,IAAA,CAAC;IAEM,KAAK,GAAG,MAAK;QAClB,IAAI,CAAC,IAAI,EAAE;AACX,QAAA,IAAI,CAAC,cAAc,GAAG,CAAC;AACvB,QAAA,IAAI,CAAC,sBAAsB,GAAG,KAAK;QACnC,IAAI,CAAC,gBAAgB,EAAE;QACvB,IAAI,CAAC,WAAW,CAAC;AACf,YAAA,MAAM,EAAE,OAAO;AACf,YAAA,WAAW,EAAE,CAAC;AACd,YAAA,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ;AAC9B,YAAA,mBAAmB,EAAE,KAAK;AAC1B,YAAA,QAAQ,EAAE,EAAE;AACb,SAAA,CAAC;AACJ,IAAA,CAAC;AAEO,IAAA,WAAW,CAAC,QAAgC,EAAA;AAClD,QAAA,IAAI,CAAC,KAAK,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,QAAQ,EAAE;QAC3C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC;IACvC;IAEQ,gBAAgB,GAAA;AACtB,QAAA,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;AAChF,QAAA,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,GAAG;AACzB,QAAA,OAAO,GAAG;IACZ;IAEQ,UAAU,GAAA;QAChB,IAAI,IAAI,CAAC,OAAO;AAAE,YAAA,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC;AAC7C,QAAA,IAAI,CAAC,OAAO,GAAG,WAAW,CAAC,MAAK;YAC9B,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE;AAC3B,gBAAA,IAAI,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;YACzD;iBAAO;gBACL,IAAI,CAAC,IAAI,EAAE;gBACX,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;YACzC;QACF,CAAC,EAAE,IAAI,CAAC;IACV;IAEQ,UAAU,GAAG,MAAK;AACxB,QAAA,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI;YAAE;AAE5E,QAAA,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,EAAE;AAC7B,QAAA,IAAI;AACF,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AACjE,YAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC;AAElE,YAAA,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,cAAc,CAAC;YAEjE,IAAI,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,EAAE;gBACxD,IAAI,CAAC,iBAAiB,EAAE;AACxB,gBAAA,OAAO;YACT;QACF;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,OAAO,CAAC,KAAK,CAAC,wCAAwC,EAAE,GAAG,CAAC;QAC9D;QAEA,IAAI,CAAC,SAAS,GAAG,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC;AACzD,IAAA,CAAC;IAEO,iBAAiB,GAAA;QACvB,IAAI,IAAI,CAAC,sBAAsB;YAAE;AAEjC,QAAA,IAAI,CAAC,sBAAsB,GAAG,IAAI;QAClC,IAAI,CAAC,WAAW,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC;QAE/C,UAAU,CAAC,MAAK;AACd,YAAA,IAAI,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE;gBACxD,IAAI,CAAC,cAAc,EAAE;AACrB,gBAAA,IAAI,CAAC,sBAAsB,GAAG,KAAK;gBACnC,IAAI,CAAC,WAAW,CAAC;oBACf,WAAW,EAAE,IAAI,CAAC,cAAc;AAChC,oBAAA,mBAAmB,EAAE,KAAK;AAC3B,iBAAA,CAAC;gBACF,IAAI,CAAC,SAAS,GAAG,qBAAqB,CAAC,IAAI,CAAC,UAAU,CAAC;YACzD;iBAAO;gBACL,IAAI,CAAC,cAAc,EAAE;YACvB;QACF,CAAC,EAAE,IAAI,CAAC;IACV;AAEQ,IAAA,WAAW,CAAC,OAAY,EAAE,OAAY,EAAE,SAAoB,EAAA;QAClE,IAAI,IAAI,CAAC,sBAAsB;AAAE,YAAA,OAAO,KAAK;AAE7C,QAAA,IAAI,SAAS,KAAK,WAAW,IAAI,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,CAAC,EAAE;YAC/D,MAAM,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;YAC9B,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3E;QAEA,IAAI,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,CAAC,EAAE;YACtC,MAAM,GAAG,GAAyB,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC;YAC1D,QAAQ,SAAS;AACf,gBAAA,KAAK,OAAO;oBACV,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc;AAC9D,gBAAA,KAAK,OAAO;oBACV,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAClD,oBAAA,OAAO,CAAC,CAAC,OAAO,GAAG,QAAQ,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,cAAc;AAChE,gBAAA,KAAK,WAAW;AACd,oBAAA,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC7D,oBAAA,OAAO,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,oBAAoB;AACzF,gBAAA,SAAS,OAAO,KAAK;;QAEzB;AACA,QAAA,OAAO,KAAK;IACd;AAEQ,IAAA,MAAM,cAAc,GAAA;QAC1B,IAAI,CAAC,IAAI,EAAE;QACX,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;AAEzC,QAAA,IAAI;YACF,MAAM,KAAK,GAAW,EAAE;YACxB,IAAI,WAAW,GAAG,EAAE;AAEpB,YAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC1B,IAAI,CAAC,IAAI,CAAC,MAAM;oBAAE;AAClB,gBAAA,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,CAAC;gBAChF,IAAI,OAAO,EAAE,aAAa,GAAG,CAAC,CAAC,EAAE;AAC/B,oBAAA,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;oBACtE,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAc,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;AACzF,oBAAA,IAAI,IAAI;AAAE,wBAAA,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;oBAC1B,IAAI,CAAC,KAAK,CAAC;AAAE,wBAAA,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,YAAY,CAAC;gBAC3D;AACA,gBAAA,MAAM,IAAI,OAAO,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAC5C;YAEA,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,mDAAgB;AACjD,YAAA,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC;AAEnF,YAAA,IAAI,MAAM,CAAC,OAAO,EAAE;gBAClB,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;gBACvC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,eAAe,EAAE,CAAC;YACvG;iBAAO;AACL,gBAAA,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,uBAAuB;AACvD,gBAAA,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACvD,gBAAA,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;YACjD;QACF;QAAE,OAAO,GAAG,EAAE;AACZ,YAAA,IAAI,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,sBAAsB,EAAE,CAAC;AACvE,YAAA,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;QAClE;IACF;IAEQ,WAAW,CAAC,KAAuB,EAAE,SAA+B,EAAA;QAC1E,MAAM,GAAG,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,IAAI,CAAE;AAClD,QAAA,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,UAAU,CAAC;AACrD,QAAA,MAAM,EAAE,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,WAAW,CAAC;QAEtD,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;AAE5B,QAAA,MAAM,KAAK,GAAG,IAAI,GAAG,IAAI;AACzB,QAAA,MAAM,MAAM,GAAG,IAAI,GAAG,IAAI;AAC1B,QAAA,MAAM,MAAM,GAAG,KAAK,GAAG,GAAG;QAE1B,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC;AAC7B,QAAA,GAAG,CAAC,SAAS,CACX,KAAK,EACL,IAAI,GAAG,MAAM,EAAE,IAAI,GAAG,MAAM,EAC5B,KAAK,GAAG,MAAM,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,CAAC,EACvC,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,CACf;QAED,OAAO,IAAI,CAAC,eAAe;IAC7B;AACD;;MC7SY,eAAe,CAAA;AAON,IAAA,MAAA;AANZ,IAAA,MAAM;AACN,IAAA,YAAY,GAAG,IAAI,eAAe,CAAuB,IAAI,CAAC;;AAG/D,IAAA,MAAM,GAAqC,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE;AAElF,IAAA,WAAA,CAAoB,MAAc,EAAA;QAAd,IAAA,CAAA,MAAM,GAAN,MAAM;IAAW;AAE9B,IAAA,IAAI,CAAC,MAA4B,EAAA;;AAEtC,QAAA,IAAI,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAK;AACjC,YAAA,IAAI,CAAC,MAAM,GAAG,IAAI,cAAc,CAAC;AAC/B,gBAAA,GAAG,MAAM;AACT,gBAAA,aAAa,EAAE,CAAC,KAAK,KAAI;;AAEvB,oBAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACtD;AACD,aAAA,CAAC;AACF,YAAA,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE;AAC1B,QAAA,CAAC,CAAC;IACJ;AAEO,IAAA,MAAM,CAAC,KAAuB,EAAA;AACnC,QAAA,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,KAAK,CAAC;IACjC;IAEO,KAAK,GAAA;AACV,QAAA,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE;IACtB;IAEO,KAAK,GAAA;AACV,QAAA,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE;IACtB;IAEA,WAAW,GAAA;AACT,QAAA,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE;IACrB;uGArCW,eAAe,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAA,EAAA,CAAA,MAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAAf,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,cADF,MAAM,EAAA,CAAA;;2FACnB,eAAe,EAAA,UAAA,EAAA,CAAA;kBAD3B,UAAU;mBAAC,EAAE,UAAU,EAAE,MAAM,EAAE;;;MCSrB,iBAAiB,CAAA;AA2BT,IAAA,QAAA;AA1BE,IAAA,SAAS;;AAGrB,IAAA,MAAM;IACN,QAAQ,GAAG,EAAE;AACb,IAAA,cAAc;AACd,IAAA,cAAc;AACd,IAAA,oBAAoB;AACpB,IAAA,oBAAoB;;IAGpB,cAAc,GAAG,EAAE;IACnB,iBAAiB,GAAG,EAAE;IACtB,UAAU,GAAG,EAAE;IACf,UAAU,GAAG,EAAE;IACf,kBAAkB,GAAG,EAAE;IACvB,WAAW,GAAG,EAAE;IAChB,gBAAgB,GAAG,EAAE;IACrB,kBAAkB,GAAG,EAAE;IACvB,mBAAmB,GAAG,EAAE;IACxB,iBAAiB,GAAG,EAAE;IACtB,mBAAmB,GAAG,EAAE;AAEvB,IAAA,UAAU,GAAG,IAAI,YAAY,EAAqB;AAClD,IAAA,OAAO,GAAG,IAAI,YAAY,EAAO;AAE3C,IAAA,WAAA,CAAmB,QAAyB,EAAA;QAAzB,IAAA,CAAA,QAAQ,GAAR,QAAQ;IAAoB;IAE/C,QAAQ,GAAA;;AAEN,QAAA,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;YACjB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,cAAc,EAAE,IAAI,CAAC,cAAc;YACnC,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;YAC/C,oBAAoB,EAAE,IAAI,CAAC,oBAAoB;AAC/C,YAAA,UAAU,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;AAC9C,YAAA,OAAO,EAAE,CAAC,GAAG,KAAK,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG;AACxC,SAAA,CAAC;IACJ;AAEA,IAAA,MAAM,eAAe,GAAA;AACnB,QAAA,IAAI;YACF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC;AACvD,gBAAA,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;AACrD,aAAA,CAAC;AACF,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa;AAC1C,YAAA,KAAK,CAAC,SAAS,GAAG,MAAM;AACxB,YAAA,KAAK,CAAC,gBAAgB,GAAG,MAAK;gBAC5B,KAAK,CAAC,IAAI,EAAE;AACZ,gBAAA,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC;AAC7B,YAAA,CAAC;QACH;QAAE,OAAO,CAAC,EAAE;AACV,YAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;QACvE;IACF;IAEA,WAAW,GAAA;QACT,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,aAAa,EAAE,SAAwB;AACtE,QAAA,MAAM,EAAE,SAAS,EAAE,CAAC,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IACpD;uGA9DW,iBAAiB,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAAA,eAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;2FAAjB,iBAAiB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,eAAA,EAAA,MAAA,EAAA,EAAA,MAAA,EAAA,QAAA,EAAA,QAAA,EAAA,UAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,oBAAA,EAAA,sBAAA,EAAA,oBAAA,EAAA,sBAAA,EAAA,cAAA,EAAA,gBAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,UAAA,EAAA,YAAA,EAAA,UAAA,EAAA,YAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,WAAA,EAAA,aAAA,EAAA,gBAAA,EAAA,kBAAA,EAAA,kBAAA,EAAA,oBAAA,EAAA,mBAAA,EAAA,qBAAA,EAAA,iBAAA,EAAA,mBAAA,EAAA,mBAAA,EAAA,qBAAA,EAAA,EAAA,OAAA,EAAA,EAAA,UAAA,EAAA,YAAA,EAAA,OAAA,EAAA,SAAA,EAAA,EAAA,WAAA,EAAA,CAAA,EAAA,YAAA,EAAA,WAAA,EAAA,KAAA,EAAA,IAAA,EAAA,SAAA,EAAA,CAAA,QAAA,CAAA,EAAA,WAAA,EAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,ECb9B,k3DAsDM,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,ED9CM,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,OAAA,EAAA,QAAA,EAAA,WAAA,EAAA,MAAA,EAAA,CAAA,OAAA,EAAA,SAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAAA,EAAA,CAAA,IAAA,EAAA,QAAA,EAAA,QAAA,EAAA,MAAA,EAAA,CAAA,MAAA,EAAA,UAAA,EAAA,UAAA,CAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,EAAA,CAAA,SAAA,EAAA,IAAA,EAAA,OAAA,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,EAAA,CAAA,aAAA,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAKX,iBAAiB,EAAA,UAAA,EAAA,CAAA;kBAR7B,SAAS;+BACE,eAAe,EAAA,UAAA,EACb,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,CAAC,EAAA,aAAA,EAER,iBAAiB,CAAC,IAAI,EAAA,QAAA,EAAA,k3DAAA,EAAA;;sBAIpC,SAAS;uBAAC,QAAQ;;sBAGlB;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBAGA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBACA;;sBAEA;;sBACA;;;AEtCH;AAWA;;;;;;AAMG;AACI,MAAM,cAAc,GAAG,OAC5B,MAAc,EACd,UAAkB,EAClB,SAAsB,KACO;AAC7B,IAAA,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE;IAE/B,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,KAAI;QAC7B,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAA,MAAA,EAAS,CAAC,CAAA,IAAA,CAAM,CAAC;AAClD,IAAA,CAAC,CAAC;AACF,IAAA,QAAQ,CAAC,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAEvD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,CAAA,EAAG,MAAM,YAAY,EAAE;AAClD,QAAA,MAAM,EAAE,MAAM;AACd,QAAA,IAAI,EAAE,QAAQ;AACf,KAAA,CAAC;AAEF,IAAA,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE;AAChB,QAAA,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC;IAChD;AAEA,IAAA,MAAM,IAAI,GAAqB,MAAM,QAAQ,CAAC,IAAI,EAAE;AACpD,IAAA,OAAO,IAAI;AACb;;;;;;;ACzCA;;AAEG;;;;"}
1
+ {"version":3,"file":"richard.fadiora-liveness-detection.mjs","sources":["../../../src/core/LivenessEngine.ts","../../../src/angular/liveness.service.ts","../../../src/angular/liveness.component.ts","../../../src/angular/liveness.component.html","../../../src/api.ts","../../../src/richard.fadiora-liveness-detection.ts"],"sourcesContent":["// src/core/LivenessEngine.ts\r\nimport { FaceLandmarker, HandLandmarker, FilesetResolver, NormalizedLandmark } from \"@mediapipe/tasks-vision\";\r\n\r\nexport type Challenge = \"Smile\" | \"Blink\" | \"Turn_Head\" | \"Thumbs_Up\";\r\n\r\nexport interface LivenessSDKResult {\r\n success: boolean;\r\n image?: string;\r\n reason?: string;\r\n skinConfidence?: number;\r\n}\r\n\r\nexport interface LivenessState {\r\n status: \"loading\" | \"ready\" | \"capturing\" | \"verifying\" | \"success\" | \"error\" | \"expired\";\r\n sequence: Challenge[];\r\n currentStep: number;\r\n timeLeft: number;\r\n isStepTransitioning: boolean;\r\n errorMsg: string;\r\n}\r\n\r\nexport interface LivenessEngineConfig {\r\n apiUrl: string;\r\n duration?: number;\r\n smileThreshold?: number;\r\n blinkThreshold?: number;\r\n minturnHeadThreshold?: number;\r\n maxturnHeadThreshold?: number;\r\n onStateChange?: (state: LivenessState) => void;\r\n onComplete?: (result: LivenessSDKResult) => void;\r\n onError?: (error: LivenessSDKResult) => void;\r\n}\r\n\r\nexport class LivenessEngine {\r\n private config: Required<LivenessEngineConfig>;\r\n private models: { face: FaceLandmarker | null; hand: HandLandmarker | null } = { face: null, hand: null };\r\n private webcam: HTMLVideoElement | null = null;\r\n private state: LivenessState;\r\n \r\n private currentStepRef = 0;\r\n private isStepTransitioningRef = false;\r\n private timerId: ReturnType<typeof setInterval> | null = null;\r\n private requestId: number | null = null;\r\n \r\n private offscreenCanvas = document.createElement(\"canvas\");\r\n private CHALLENGE_POOL: Challenge[] = [\"Smile\", \"Blink\", \"Turn_Head\", \"Thumbs_Up\"];\r\n\r\n constructor(config: LivenessEngineConfig) {\r\n this.config = {\r\n apiUrl: config.apiUrl,\r\n duration: config.duration ?? 60,\r\n smileThreshold: config.smileThreshold ?? 0.2,\r\n blinkThreshold: config.blinkThreshold ?? 0.012,\r\n minturnHeadThreshold: config.minturnHeadThreshold ?? 0.15,\r\n maxturnHeadThreshold: config.maxturnHeadThreshold ?? 0.85,\r\n onStateChange: config.onStateChange || (() => {}),\r\n onComplete: config.onComplete || (() => {}),\r\n onError: config.onError || (() => {}),\r\n };\r\n\r\n this.state = {\r\n status: \"loading\",\r\n sequence: [],\r\n currentStep: 0,\r\n timeLeft: this.config.duration,\r\n isStepTransitioning: false,\r\n errorMsg: \"\",\r\n };\r\n \r\n this.offscreenCanvas.width = 224;\r\n this.offscreenCanvas.height = 224;\r\n }\r\n\r\n /**\r\n * Phase 1: Load AI Assets (Call this immediately on mount)\r\n */\r\n public async loadModels() {\r\n console.log(\"[LivenessEngine] Loading AI Models...\");\r\n try {\r\n const vision = await FilesetResolver.forVisionTasks(\r\n \"https://cdn.jsdelivr.net/npm/@mediapipe/tasks-vision@0.10.3/wasm\"\r\n );\r\n\r\n this.models.face = await FaceLandmarker.createFromOptions(vision, {\r\n baseOptions: {\r\n modelAssetPath: \"https://storage.googleapis.com/mediapipe-models/face_landmarker/face_landmarker/float16/1/face_landmarker.task\",\r\n delegate: \"GPU\",\r\n },\r\n outputFaceBlendshapes: true,\r\n runningMode: \"VIDEO\",\r\n });\r\n\r\n this.models.hand = await HandLandmarker.createFromOptions(vision, {\r\n baseOptions: {\r\n modelAssetPath: \"https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task\",\r\n delegate: \"GPU\",\r\n },\r\n runningMode: \"VIDEO\",\r\n numHands: 1,\r\n });\r\n\r\n this.generateSequence();\r\n // Stay in 'loading' or move to a 'ready_to_mount' status if you prefer\r\n // For now, we move to ready to signal the UI can show the camera\r\n this.updateState({ status: \"ready\" });\r\n console.log(\"[LivenessEngine] Models Loaded.\");\r\n } catch (err) {\r\n console.error(\"[LivenessEngine] Init Error:\", err);\r\n this.updateState({ status: \"error\", errorMsg: \"Failed to load security assets.\" });\r\n }\r\n }\r\n\r\n /**\r\n * Phase 2: Attach Video (Call this once the Webcam is in the DOM)\r\n */\r\n public attachVideo = (video: HTMLVideoElement) => {\r\n this.webcam = video;\r\n console.log(\"[LivenessEngine] Video stream attached.\");\r\n };\r\n\r\n public start = () => {\r\n if (this.state.status !== \"ready\" || !this.webcam) {\r\n console.warn(\"[LivenessEngine] Engine not ready or video missing.\");\r\n return;\r\n }\r\n\r\n console.log(\"[LivenessEngine] Session Starting...\");\r\n this.state.status = \"capturing\"; // Sync internal status immediately\r\n this.updateState({ status: \"capturing\" });\r\n this.startTimer();\r\n this.detectLoop();\r\n };\r\n\r\n public stop = () => {\r\n if (this.timerId) clearInterval(this.timerId);\r\n if (this.requestId) cancelAnimationFrame(this.requestId);\r\n this.timerId = null;\r\n this.requestId = null;\r\n };\r\n\r\n public reset = () => {\r\n this.stop();\r\n this.currentStepRef = 0;\r\n this.isStepTransitioningRef = false;\r\n this.generateSequence();\r\n this.updateState({\r\n status: \"ready\",\r\n currentStep: 0,\r\n timeLeft: this.config.duration,\r\n isStepTransitioning: false,\r\n errorMsg: \"\",\r\n });\r\n };\r\n\r\n private updateState(newState: Partial<LivenessState>) {\r\n this.state = { ...this.state, ...newState };\r\n this.config.onStateChange(this.state);\r\n }\r\n\r\n private generateSequence(): Challenge[] {\r\n const seq = [...this.CHALLENGE_POOL].sort(() => 0.5 - Math.random()).slice(0, 3);\r\n this.state.sequence = seq;\r\n return seq;\r\n }\r\n\r\n private startTimer() {\r\n if (this.timerId) clearInterval(this.timerId);\r\n this.timerId = setInterval(() => {\r\n if (this.state.timeLeft > 0) {\r\n this.updateState({ timeLeft: this.state.timeLeft - 1 });\r\n } else {\r\n this.stop();\r\n this.updateState({ status: \"expired\" });\r\n }\r\n }, 1000);\r\n }\r\n\r\n private detectLoop = () => {\r\n if (this.state.status !== \"capturing\" || !this.webcam || !this.models.face) return;\r\n\r\n const now = performance.now();\r\n try {\r\n const faceRes = this.models.face.detectForVideo(this.webcam, now);\r\n const handRes = this.models.hand?.detectForVideo(this.webcam, now);\r\n\r\n const currentChallenge = this.state.sequence[this.currentStepRef];\r\n\r\n if (this.checkAction(faceRes, handRes, currentChallenge)) {\r\n this.handleStepSuccess();\r\n return; // handleStepSuccess schedules the next frame after a delay\r\n }\r\n } catch (err) {\r\n console.error(\"[LivenessEngine] Loop detection error:\", err);\r\n }\r\n\r\n this.requestId = requestAnimationFrame(this.detectLoop);\r\n };\r\n\r\n private handleStepSuccess() {\r\n if (this.isStepTransitioningRef) return;\r\n \r\n this.isStepTransitioningRef = true;\r\n this.updateState({ isStepTransitioning: true });\r\n\r\n setTimeout(() => {\r\n if (this.currentStepRef < this.state.sequence.length - 1) {\r\n this.currentStepRef++;\r\n this.isStepTransitioningRef = false;\r\n this.updateState({\r\n currentStep: this.currentStepRef,\r\n isStepTransitioning: false,\r\n });\r\n this.requestId = requestAnimationFrame(this.detectLoop);\r\n } else {\r\n this.sendFinalProof();\r\n }\r\n }, 1500);\r\n }\r\n\r\n private checkAction(faceRes: any, handRes: any, challenge: Challenge): boolean {\r\n if (this.isStepTransitioningRef) return false;\r\n\r\n if (challenge === \"Thumbs_Up\" && handRes?.landmarks?.length > 0) {\r\n const l = handRes.landmarks[0];\r\n return l[4].y < l[2].y && [8, 12, 16, 20].every(i => l[i].y > l[i - 2].y);\r\n }\r\n\r\n if (faceRes?.faceLandmarks?.length > 0) {\r\n const lms: NormalizedLandmark[] = faceRes.faceLandmarks[0];\r\n switch (challenge) {\r\n case \"Smile\":\r\n return (lms[291].x - lms[61].x) > this.config.smileThreshold;\r\n case \"Blink\":\r\n const leftEar = Math.abs(lms[159].y - lms[145].y);\r\n const rightEar = Math.abs(lms[386].y - lms[374].y);\r\n return ((leftEar + rightEar) / 2) < this.config.blinkThreshold;\r\n case \"Turn_Head\":\r\n const yaw = (lms[1].x - lms[33].x) / (lms[263].x - lms[33].x);\r\n return yaw < this.config.minturnHeadThreshold || yaw > this.config.maxturnHeadThreshold;\r\n default: return false;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n private async sendFinalProof() {\r\n this.stop();\r\n this.updateState({ status: \"verifying\" });\r\n\r\n try {\r\n const blobs: Blob[] = [];\r\n let finalBase64 = \"\";\r\n\r\n for (let i = 0; i < 5; i++) {\r\n if (!this.webcam) break;\r\n const faceRes = this.models.face?.detectForVideo(this.webcam, performance.now());\r\n if (faceRes?.faceLandmarks?.[0]) {\r\n const canvas = this.getFaceCrop(this.webcam, faceRes.faceLandmarks[0]);\r\n const blob = await new Promise<Blob | null>(res => canvas.toBlob(res, \"image/jpeg\", 0.9));\r\n if (blob) blobs.push(blob);\r\n if (i === 4) finalBase64 = canvas.toDataURL(\"image/jpeg\");\r\n }\r\n await new Promise(r => setTimeout(r, 100));\r\n }\r\n\r\n const { verifyLiveness } = await import(\"../api\");\r\n const result = await verifyLiveness(this.config.apiUrl, blobs, this.state.sequence);\r\n\r\n if (result.is_live) {\r\n this.updateState({ status: \"success\" });\r\n this.config.onComplete({ success: true, image: finalBase64, skinConfidence: result.skin_confidence });\r\n } else {\r\n const reason = result.reason || \"Liveness check failed\";\r\n this.updateState({ status: \"error\", errorMsg: reason });\r\n this.config.onError({ success: false, reason });\r\n }\r\n } catch (err) {\r\n this.updateState({ status: \"error\", errorMsg: \"Verification failed.\" });\r\n this.config.onError({ success: false, reason: \"Network error\" });\r\n }\r\n }\r\n\r\n private getFaceCrop(video: HTMLVideoElement, landmarks: NormalizedLandmark[]): HTMLCanvasElement {\r\n const ctx = this.offscreenCanvas.getContext(\"2d\")!;\r\n const xs = landmarks.map(l => l.x * video.videoWidth);\r\n const ys = landmarks.map(l => l.y * video.videoHeight);\r\n \r\n const minX = Math.min(...xs);\r\n const maxX = Math.max(...xs);\r\n const minY = Math.min(...ys);\r\n const maxY = Math.max(...ys);\r\n \r\n const width = maxX - minX;\r\n const height = maxY - minY;\r\n const margin = width * 0.3;\r\n\r\n ctx.clearRect(0, 0, 224, 224);\r\n ctx.drawImage(\r\n video,\r\n minX - margin, minY - margin, \r\n width + margin * 2, height + margin * 2,\r\n 0, 0, 224, 224\r\n );\r\n\r\n return this.offscreenCanvas;\r\n }\r\n}","import { Injectable, NgZone, OnDestroy } from '@angular/core';\r\nimport { BehaviorSubject, Observable } from 'rxjs';\r\nimport { LivenessEngine, LivenessState, LivenessEngineConfig } from '../core/LivenessEngine';\r\n\r\n@Injectable({ providedIn: 'root' })\r\nexport class LivenessService implements OnDestroy {\r\n private engine!: LivenessEngine;\r\n private stateSubject = new BehaviorSubject<LivenessState | null>(null);\r\n \r\n // Components will subscribe to this\r\n public state$: Observable<LivenessState | null> = this.stateSubject.asObservable();\r\n\r\n constructor(private ngZone: NgZone) {}\r\n\r\n public init(config: LivenessEngineConfig) {\r\n // Run inside runOutsideAngular to prevent 60fps change detection triggers\r\n this.ngZone.runOutsideAngular(() => {\r\n this.engine = new LivenessEngine({\r\n ...config,\r\n onStateChange: (state) => {\r\n // Bring state changes BACK into the zone so the UI updates\r\n this.ngZone.run(() => this.stateSubject.next(state));\r\n }\r\n });\r\n this.engine.loadModels();\r\n });\r\n }\r\n\r\n public attach(video: HTMLVideoElement) {\r\n this.engine?.attachVideo(video);\r\n }\r\n\r\n public start() {\r\n this.engine?.start();\r\n }\r\n\r\n public reset() {\r\n this.engine?.reset();\r\n }\r\n\r\n ngOnDestroy() {\r\n this.engine?.stop();\r\n }\r\n}","import { Component, ElementRef, ViewChild, AfterViewInit, Input, Output, EventEmitter, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';\r\nimport { LivenessService } from './liveness.service';\r\nimport { LivenessSDKResult, Challenge, LivenessState } from '../core/LivenessEngine';\r\nimport { CommonModule } from '@angular/common'\r\n\r\n@Component({\r\n selector: 'LivenessCheck',\r\n standalone: true, // Make it standalone\r\n imports: [CommonModule], // Add CommonModule here to fix NG8002\r\n templateUrl: './liveness.component.html',\r\n encapsulation: ViewEncapsulation.None,\r\n})\r\n\r\nexport class LivenessComponent implements OnInit, AfterViewInit, OnDestroy {\r\n @ViewChild('webcam') webcamRef!: ElementRef<HTMLVideoElement>;\r\n\r\n // --- Logic Configuration ---\r\n @Input() apiUrl!: string;\r\n @Input() duration = 60;\r\n @Input() smileThreshold?: number;\r\n @Input() blinkThreshold?: number;\r\n @Input() minturnHeadThreshold?: number;\r\n @Input() maxturnHeadThreshold?: number;\r\n\r\n // --- Styling Configuration (Class Names) ---\r\n @Input() containerClass = '';\r\n @Input() videoWrapperClass = '';\r\n @Input() videoClass = '';\r\n @Input() timerClass = '';\r\n @Input() stateClass = '';\r\n @Input() challengeTextClass = '';\r\n @Input() buttonClass = '';\r\n @Input() retryButtonClass = '';\r\n @Input() statusOverlayClass = '';\r\n @Input() successMessageClass = '';\r\n @Input() errorMessageClass = '';\r\n @Input() instructionBoxClass = '';\r\n\r\n // --- Content Configuration ---\r\n @Input() successMessage = 'Verification Successful!';\r\n @Input() errorMessage = 'Verification failed. Please try again.';\r\n @Input() retryButtonLabel = 'Try Again';\r\n @Input() startButtonLabel = 'Start Verification';\r\n @Input() challengeMapping: Record<Challenge, string> = {\r\n 'Smile': 'Smile',\r\n 'Blink': 'Blink',\r\n 'Turn_Head': 'Turn Head',\r\n 'Thumbs_Up': 'Thumbs Up' // Default with a space\r\n };\r\n\r\n @Output() onComplete = new EventEmitter<LivenessSDKResult>();\r\n @Output() onError = new EventEmitter<any>();\r\n @Output() onStateChange = new EventEmitter<LivenessState | null>();\r\n\r\n constructor(public liveness: LivenessService) {}\r\n\r\n ngOnInit() {\r\n // Pass the custom thresholds directly to the service\r\n this.liveness.init({\r\n apiUrl: this.apiUrl,\r\n duration: this.duration,\r\n smileThreshold: this.smileThreshold,\r\n blinkThreshold: this.blinkThreshold,\r\n minturnHeadThreshold: this.minturnHeadThreshold,\r\n maxturnHeadThreshold: this.maxturnHeadThreshold,\r\n onComplete: (res) => this.onComplete.emit(res),\r\n onError: (err) => this.onError.emit(err)\r\n });\r\n\r\n this.liveness.state$.subscribe(state => {\r\n // This bridges the Service to the Component's @Output\r\n this.onStateChange.emit(state);\r\n });\r\n\r\n }\r\n\r\n async ngAfterViewInit() {\r\n try {\r\n const stream = await navigator.mediaDevices.getUserMedia({ \r\n video: { width: 640, height: 480, facingMode: 'user' } \r\n });\r\n const video = this.webcamRef.nativeElement;\r\n video.srcObject = stream;\r\n video.onloadedmetadata = () => {\r\n video.play();\r\n this.liveness.attach(video);\r\n };\r\n } catch (e) {\r\n this.onError.emit({ success: false, reason: 'Camera access denied' });\r\n }\r\n }\r\n\r\n ngOnDestroy() {\r\n const stream = this.webcamRef?.nativeElement?.srcObject as MediaStream;\r\n stream?.getTracks().forEach(track => track.stop());\r\n }\r\n}","<div [ngClass]=\"containerClass\">\r\n <div [ngClass]=\"videoWrapperClass\">\r\n <video #webcam \r\n autoplay \r\n playsinline \r\n muted \r\n [ngClass]=\"videoClass\" \r\n style=\"transform: scaleX(-1);\">\r\n </video>\r\n\r\n <ng-container *ngIf=\"(liveness.state$ | async) as state\">\r\n <div [ngClass]=\"statusOverlayClass\">\r\n \r\n <div class=\"instruction-box\">\r\n <h2 class=\"status-text\" [ngClass] = \"stateClass\">{{ state.status | uppercase }}</h2>\r\n \r\n <p *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"challengeTextClass\">\r\n {{ challengeMapping[state.sequence[state.currentStep]] }}\r\n </p>\r\n\r\n <p *ngIf=\"state.status === 'success'\"\r\n [ngClass]=\"successMessageClass\">\r\n {{ successMessage }}\r\n </p>\r\n \r\n <p *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n [ngClass]=\"errorMessageClass\">\r\n {{ errorMessage }}\r\n </p>\r\n </div>\r\n\r\n <div *ngIf=\"state.status === 'capturing'\" \r\n [ngClass]=\"timerClass\">\r\n {{ state.timeLeft }}\r\n </div>\r\n\r\n <div class=\"action-area\">\r\n <button *ngIf=\"state.status === 'ready'\" \r\n (click)=\"liveness.start()\"\r\n [ngClass]=\"buttonClass\">\r\n {{ startButtonLabel }}\r\n </button>\r\n \r\n <button *ngIf=\"state.status === 'error' || state.status === 'expired'\" \r\n (click)=\"liveness.reset()\"\r\n [ngClass]=\"retryButtonClass\">\r\n {{ retryButtonLabel }}\r\n </button>\r\n </div>\r\n\r\n </div>\r\n </ng-container>\r\n </div>\r\n</div>","// src/api.ts\r\n\r\nimport { Challenge } from \"./core/LivenessEngine\";\r\n\r\nexport interface LivenessResponse {\r\n is_live: boolean;\r\n reason?: string;\r\n skin_confidence?: number;\r\n [key: string]: any; // allow extra fields if backend returns more\r\n}\r\n\r\n/**\r\n * Sends captured frames to the backend API for liveness verification.\r\n * @param apiUrl - Base URL of the liveness backend\r\n * @param frameBlobs - Array of Blob objects representing captured frames\r\n * @param challenge - The current challenge string\r\n * @returns LivenessResponse from backend\r\n */\r\nexport const verifyLiveness = async (\r\n apiUrl: string,\r\n frameBlobs: Blob[],\r\n challenge: Challenge[]\r\n): Promise<LivenessResponse> => {\r\n const formData = new FormData();\r\n\r\n frameBlobs.forEach((blob, i) => {\r\n formData.append('files', blob, `frame_${i}.jpg`);\r\n });\r\n formData.append('challenge', JSON.stringify(challenge));\r\n\r\n const response = await fetch(`${apiUrl}/v1/verify`, {\r\n method: 'POST',\r\n body: formData,\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(\"Network response was not ok\");\r\n }\r\n\r\n const data: LivenessResponse = await response.json();\r\n return data;\r\n};","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["i1.LivenessService"],"mappings":"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"}
@@ -89,6 +89,7 @@ declare class LivenessComponent implements OnInit, AfterViewInit, OnDestroy {
89
89
  videoWrapperClass: string;
90
90
  videoClass: string;
91
91
  timerClass: string;
92
+ stateClass: string;
92
93
  challengeTextClass: string;
93
94
  buttonClass: string;
94
95
  retryButtonClass: string;
@@ -96,14 +97,20 @@ declare class LivenessComponent implements OnInit, AfterViewInit, OnDestroy {
96
97
  successMessageClass: string;
97
98
  errorMessageClass: string;
98
99
  instructionBoxClass: string;
100
+ successMessage: string;
101
+ errorMessage: string;
102
+ retryButtonLabel: string;
103
+ startButtonLabel: string;
104
+ challengeMapping: Record<Challenge, string>;
99
105
  onComplete: EventEmitter<LivenessSDKResult>;
100
106
  onError: EventEmitter<any>;
107
+ onStateChange: EventEmitter<LivenessState>;
101
108
  constructor(liveness: LivenessService);
102
109
  ngOnInit(): void;
103
110
  ngAfterViewInit(): Promise<void>;
104
111
  ngOnDestroy(): void;
105
112
  static ɵfac: i0.ɵɵFactoryDeclaration<LivenessComponent, never>;
106
- static ɵcmp: i0.ɵɵComponentDeclaration<LivenessComponent, "LivenessCheck", never, { "apiUrl": { "alias": "apiUrl"; "required": false; }; "duration": { "alias": "duration"; "required": false; }; "smileThreshold": { "alias": "smileThreshold"; "required": false; }; "blinkThreshold": { "alias": "blinkThreshold"; "required": false; }; "minturnHeadThreshold": { "alias": "minturnHeadThreshold"; "required": false; }; "maxturnHeadThreshold": { "alias": "maxturnHeadThreshold"; "required": false; }; "containerClass": { "alias": "containerClass"; "required": false; }; "videoWrapperClass": { "alias": "videoWrapperClass"; "required": false; }; "videoClass": { "alias": "videoClass"; "required": false; }; "timerClass": { "alias": "timerClass"; "required": false; }; "challengeTextClass": { "alias": "challengeTextClass"; "required": false; }; "buttonClass": { "alias": "buttonClass"; "required": false; }; "retryButtonClass": { "alias": "retryButtonClass"; "required": false; }; "statusOverlayClass": { "alias": "statusOverlayClass"; "required": false; }; "successMessageClass": { "alias": "successMessageClass"; "required": false; }; "errorMessageClass": { "alias": "errorMessageClass"; "required": false; }; "instructionBoxClass": { "alias": "instructionBoxClass"; "required": false; }; }, { "onComplete": "onComplete"; "onError": "onError"; }, never, never, true, never>;
113
+ static ɵcmp: i0.ɵɵComponentDeclaration<LivenessComponent, "LivenessCheck", never, { "apiUrl": { "alias": "apiUrl"; "required": false; }; "duration": { "alias": "duration"; "required": false; }; "smileThreshold": { "alias": "smileThreshold"; "required": false; }; "blinkThreshold": { "alias": "blinkThreshold"; "required": false; }; "minturnHeadThreshold": { "alias": "minturnHeadThreshold"; "required": false; }; "maxturnHeadThreshold": { "alias": "maxturnHeadThreshold"; "required": false; }; "containerClass": { "alias": "containerClass"; "required": false; }; "videoWrapperClass": { "alias": "videoWrapperClass"; "required": false; }; "videoClass": { "alias": "videoClass"; "required": false; }; "timerClass": { "alias": "timerClass"; "required": false; }; "stateClass": { "alias": "stateClass"; "required": false; }; "challengeTextClass": { "alias": "challengeTextClass"; "required": false; }; "buttonClass": { "alias": "buttonClass"; "required": false; }; "retryButtonClass": { "alias": "retryButtonClass"; "required": false; }; "statusOverlayClass": { "alias": "statusOverlayClass"; "required": false; }; "successMessageClass": { "alias": "successMessageClass"; "required": false; }; "errorMessageClass": { "alias": "errorMessageClass"; "required": false; }; "instructionBoxClass": { "alias": "instructionBoxClass"; "required": false; }; "successMessage": { "alias": "successMessage"; "required": false; }; "errorMessage": { "alias": "errorMessage"; "required": false; }; "retryButtonLabel": { "alias": "retryButtonLabel"; "required": false; }; "startButtonLabel": { "alias": "startButtonLabel"; "required": false; }; "challengeMapping": { "alias": "challengeMapping"; "required": false; }; }, { "onComplete": "onComplete"; "onError": "onError"; "onStateChange": "onStateChange"; }, never, never, true, never>;
107
114
  }
108
115
 
109
116
  interface LivenessResponse {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richard.fadiora/liveness-detection",
3
- "version": "4.3.8",
3
+ "version": "4.3.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "files": [