@moveris/shared 2.0.0 → 2.1.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/README.md CHANGED
@@ -80,11 +80,33 @@ const result = await client.fastCheck(frames, {
80
80
 
81
81
  ##### `fastCheckCrops(crops, options)`
82
82
 
83
- Perform fast liveness check with pre-cropped 224x224 face images. Use this when you handle face detection client-side.
83
+ Perform fast liveness check with pre-cropped 224x224 face images. Use this when you handle face detection client-side. Expects `CropData[]` (no timestamps — only `index` and `pixels`).
84
84
 
85
85
  ```typescript
86
+ import type { CropData } from '@moveris/shared';
87
+
88
+ const crops: CropData[] = capturedFrames.map((f) => ({
89
+ index: f.index,
90
+ pixels: f.pixels, // base64 224x224 PNG
91
+ }));
92
+
86
93
  const result = await client.fastCheckCrops(crops, {
87
- model: '50',
94
+ model: '10',
95
+ source: 'live',
96
+ });
97
+ ```
98
+
99
+ ##### `streamFrame(frame, options)`
100
+
101
+ Send a single frame to the streaming endpoint. Returns the API response which may include a partial or final result. Used internally by `useLiveness` for sequential streaming.
102
+
103
+ ```typescript
104
+ const response = await client.streamFrame(frame, {
105
+ sessionId: 'uuid',
106
+ model: '10',
107
+ source: 'live',
108
+ frameIndex: 0,
109
+ totalFrames: 10,
88
110
  });
89
111
  ```
90
112
 
@@ -160,14 +182,23 @@ const job = await client.waitForJobResult('job-uuid', 30);
160
182
  Model selection for liveness detection speed/accuracy trade-off.
161
183
 
162
184
  ```typescript
163
- type FastCheckModel = '10' | '50' | '250';
185
+ type FastCheckModel =
186
+ | '10'
187
+ | '50'
188
+ | '250' // Standard models
189
+ | 'hybrid-v2-10'
190
+ | 'hybrid-v2-50' // Hybrid V2 (physiological features)
191
+ | 'mixed-10'; // Mixed (visual + physiological scoring)
164
192
  ```
165
193
 
166
- | Value | Frames | Description |
167
- | ------- | ------ | --------------------------------- |
168
- | `'10'` | 10 | Fast verification, lowest latency |
169
- | `'50'` | 50 | Balanced speed and accuracy |
170
- | `'250'` | 250 | Highest accuracy, slower |
194
+ | Value | Frames | Category | Description |
195
+ | ---------------- | ------ | --------- | ----------------------------------------- |
196
+ | `'10'` | 10 | Standard | Fast verification, lowest latency |
197
+ | `'50'` | 50 | Standard | Balanced speed and accuracy |
198
+ | `'250'` | 250 | Standard | Highest accuracy, slower |
199
+ | `'hybrid-v2-10'` | 10 | Hybrid V2 | Visual + physiological feature extraction |
200
+ | `'hybrid-v2-50'` | 50 | Hybrid V2 | Higher accuracy physiological features |
201
+ | `'mixed-10'` | 10 | Mixed | Combined visual + physiological scoring |
171
202
 
172
203
  #### FrameSource
173
204
 
@@ -236,6 +267,17 @@ interface CapturedFrame {
236
267
  }
237
268
  ```
238
269
 
270
+ #### CropData
271
+
272
+ Pre-cropped face image for the `fast-check-crops` endpoint. Omits `timestampMs` since timing is not needed for cropped submissions.
273
+
274
+ ```typescript
275
+ interface CropData {
276
+ index: number; // Frame sequence number (0-based)
277
+ pixels: string; // Base64-encoded 224x224 PNG image
278
+ }
279
+ ```
280
+
239
281
  ---
240
282
 
241
283
  ### Utilities
@@ -424,10 +466,28 @@ const inOval = isFaceInOval(faceBbox, ovalRegion);
424
466
  console.log(inOval.isInOval); // true/false
425
467
  console.log(inOval.alignmentScore); // 0-1
426
468
 
427
- // Calculate crop region for face
469
+ // Calculate crop region for face (224x224, aligned with cognito-check constants)
428
470
  const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
471
+ // { x, y, size } — square crop region in pixel coordinates
472
+
473
+ // Calculate adaptive crop multiplier based on face size
474
+ import { calculateAdaptiveCropMultiplier } from '@moveris/shared';
475
+ const multiplier = calculateAdaptiveCropMultiplier(faceSize, videoSize);
476
+ // Returns 1.8–2.5x (matched to cognito-check for ~50% face coverage)
429
477
  ```
430
478
 
479
+ #### Crop Constants (aligned with cognito-check)
480
+
481
+ These constants control how face crops are generated for the `fast-check-crops` endpoint:
482
+
483
+ | Constant | Value | Description |
484
+ | -------------------------------- | ----- | ------------------------------------------------ |
485
+ | `IDEAL_CROP_MULTIPLIER` | 2.0 | Default crop region = 2x face size |
486
+ | `MIN_CROP_MULTIPLIER` | 1.8 | Minimum crop (face very close) |
487
+ | `MAX_CROP_MULTIPLIER` | 2.5 | Maximum crop (face very far) |
488
+ | `FACE_CENTER_VERTICAL_OFFSET` | 0.05 | Slight upward shift to include forehead for rPPG |
489
+ | `TARGET_FACE_PERCENTAGE_IN_CROP` | 0.5 | Face should occupy ~50% of 224x224 crop |
490
+
431
491
  ---
432
492
 
433
493
  ## Configuration Constants
@@ -456,7 +516,14 @@ import type {
456
516
  FrameSource,
457
517
  Verdict,
458
518
  CapturedFrame,
519
+ CropData,
459
520
  LivenessClientConfig,
521
+ DetectionResult,
522
+ DetectionSummary,
523
+ FaceBoundingBox,
524
+ HeadPose,
525
+ FaceLandmarkPoint,
526
+ LandmarkValidationResult,
460
527
  } from '@moveris/shared';
461
528
  ```
462
529
 
package/dist/index.d.mts CHANGED
@@ -204,6 +204,204 @@ interface LivenessCallbacks {
204
204
  onStateChange?: OnStateChangeCallback;
205
205
  }
206
206
 
207
+ interface FaceBoundingBox {
208
+ originX: number;
209
+ originY: number;
210
+ width: number;
211
+ height: number;
212
+ }
213
+ interface BlurAnalysis {
214
+ variance: number;
215
+ isBlurry: boolean;
216
+ threshold: number;
217
+ }
218
+ interface LightingAnalysis {
219
+ faceBrightness: number;
220
+ backgroundBrightness: number;
221
+ ratio: number;
222
+ status: 'good' | 'backlit' | 'low_light';
223
+ warning?: string;
224
+ }
225
+ interface FaceVisibilityResult {
226
+ visible: boolean;
227
+ reason?: string;
228
+ }
229
+ interface FaceAlignmentResult {
230
+ score: number;
231
+ tooClose: boolean;
232
+ tooFar: boolean;
233
+ }
234
+ interface OvalRegion {
235
+ centerX: number;
236
+ centerY: number;
237
+ width: number;
238
+ height: number;
239
+ }
240
+ interface FaceInOvalResult {
241
+ isInside: boolean;
242
+ centerInOval: boolean;
243
+ sizeMatch: boolean;
244
+ feedback?: string;
245
+ }
246
+ interface FrameQualityResult {
247
+ passed: boolean;
248
+ blur?: BlurAnalysis;
249
+ lighting?: LightingAnalysis;
250
+ visibility?: FaceVisibilityResult;
251
+ alignment?: FaceAlignmentResult;
252
+ rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
253
+ }
254
+ declare const DEFAULT_BLUR_THRESHOLD = 100;
255
+ declare const BLUR_THRESHOLD_MOBILE = 150;
256
+ declare const BACKLIT_RATIO_THRESHOLD = 0.6;
257
+ declare const LOW_LIGHT_THRESHOLD = 50;
258
+ declare const MIN_FACE_TOP_MARGIN = 0.1;
259
+ declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
260
+ declare const MIN_FACE_SIDE_MARGIN = 0.05;
261
+ declare const MIN_CAPTURE_ALIGNMENT = 0.6;
262
+ declare const HIGH_ALIGNMENT = 0.85;
263
+ declare const GOOD_ALIGNMENT = 0.5;
264
+ declare const IDEAL_CROP_MULTIPLIER = 2;
265
+ declare const MIN_CROP_MULTIPLIER = 1.8;
266
+ declare const MAX_CROP_MULTIPLIER = 2.5;
267
+ declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
268
+ declare const MIN_FACE_RATIO = 0.036;
269
+ declare const MAX_FACE_RATIO = 0.7;
270
+ declare const FACE_CROP_OUTPUT_SIZE = 224;
271
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
272
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
273
+ declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
274
+ declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
275
+ declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
276
+ declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
277
+ declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
278
+ declare const DEFAULT_OVAL_REGION: OvalRegion;
279
+ declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
280
+ declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
281
+ declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
282
+ declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
283
+ declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
284
+ x: number;
285
+ y: number;
286
+ size: number;
287
+ };
288
+ declare function checkFrameQuality(options: {
289
+ faceBox?: FaceBoundingBox;
290
+ frameWidth: number;
291
+ frameHeight: number;
292
+ blurAnalysis?: BlurAnalysis;
293
+ lightingAnalysis?: LightingAnalysis;
294
+ minAlignment?: number;
295
+ }): FrameQualityResult;
296
+ declare class BaseFrameCollector {
297
+ protected frames: CapturedFrame[];
298
+ protected maxFrames: number;
299
+ protected startTime: number;
300
+ constructor(maxFrames?: number);
301
+ setMaxFrames(max: number): void;
302
+ addFrame(frame: CapturedFrame): void;
303
+ getFrames(): CapturedFrame[];
304
+ getCount(): number;
305
+ isComplete(): boolean;
306
+ reset(): void;
307
+ getStartTime(): number;
308
+ getNextIndex(): number;
309
+ }
310
+
311
+ interface FaceLandmarkPoint {
312
+ x: number;
313
+ y: number;
314
+ z: number;
315
+ }
316
+ interface LandmarkValidationResult {
317
+ valid: boolean;
318
+ message?: string;
319
+ }
320
+ declare const LANDMARK_INDEX: {
321
+ readonly NOSE_TIP: 1;
322
+ readonly UPPER_LIP: 13;
323
+ readonly LOWER_LIP: 14;
324
+ };
325
+ declare const LANDMARK_MIN_BOUND = 0.1;
326
+ declare const LANDMARK_MAX_BOUND = 0.9;
327
+ declare const MIN_LANDMARK_COUNT = 15;
328
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
329
+
330
+ interface DetectionResult {
331
+ type: string;
332
+ passed: boolean;
333
+ confidence: number;
334
+ message?: string;
335
+ metadata?: Record<string, unknown>;
336
+ }
337
+ interface DetectorConfig {
338
+ enabled: boolean;
339
+ minConfidence?: number;
340
+ }
341
+ interface DetectionSummary {
342
+ allPassed: boolean;
343
+ results: Map<string, DetectionResult>;
344
+ faceBox?: FaceBoundingBox;
345
+ faceLandmarks?: FaceLandmarkPoint[];
346
+ warnings: string[];
347
+ }
348
+ interface HeadPose {
349
+ yawDeg: number;
350
+ pitchDeg: number;
351
+ rollDeg: number;
352
+ }
353
+ interface GazeThresholds {
354
+ maxYaw: number;
355
+ maxPitch: number;
356
+ }
357
+ declare const DEFAULT_GAZE_THRESHOLDS: GazeThresholds;
358
+ interface HandOcclusionConfig {
359
+ faceExpansionFactor: number;
360
+ minLandmarksForOcclusion: number;
361
+ faceBoxExpiryMs: number;
362
+ centerRegion: number;
363
+ }
364
+ declare const DEFAULT_HAND_OCCLUSION_CONFIG: HandOcclusionConfig;
365
+ interface FaceDetectionTiers {
366
+ primaryConfidence: number;
367
+ secondaryConfidence: number;
368
+ }
369
+ declare const DEFAULT_FACE_DETECTION_TIERS: FaceDetectionTiers;
370
+ interface VideoFrameMetadata {
371
+ presentationTime: number;
372
+ expectedDisplayTime: number;
373
+ width: number;
374
+ height: number;
375
+ mediaTime: number;
376
+ presentedFrames: number;
377
+ processingDuration?: number;
378
+ }
379
+ interface StabilizationProgress {
380
+ elapsed: number;
381
+ stableFrameCount: number;
382
+ requiredFrames: number;
383
+ greenMean: number;
384
+ greenDelta: number | null;
385
+ threshold: number;
386
+ isStable: boolean;
387
+ progress: number;
388
+ }
389
+ interface StabilizationResult {
390
+ success: boolean;
391
+ elapsed: number;
392
+ finalGreenMean: number;
393
+ stableFrameCount: number;
394
+ message: string;
395
+ }
396
+ interface StabilizerConfig {
397
+ stabilityThreshold: number;
398
+ requiredStableFrames: number;
399
+ maxWaitMs: number;
400
+ checkIntervalMs: number;
401
+ sampleSize: number;
402
+ }
403
+ declare const DEFAULT_STABILIZER_CONFIG: StabilizerConfig;
404
+
207
405
  interface LivenessClientConfig {
208
406
  baseUrl?: string;
209
407
  apiKey: string;
@@ -244,7 +442,12 @@ declare class LivenessClient {
244
442
  model?: FastCheckModel;
245
443
  source?: FrameSource;
246
444
  }): Promise<LivenessResult>;
247
- private sendStreamFrame;
445
+ streamFrame(frame: CapturedFrame, options: {
446
+ sessionId: string;
447
+ model?: FastCheckModel;
448
+ source?: FrameSource;
449
+ }): Promise<FastCheckStreamResponse>;
450
+ private sendStreamFrameInternal;
248
451
  fastCheckStream(frames: CapturedFrame[], options?: {
249
452
  sessionId?: string;
250
453
  model?: FastCheckModel;
@@ -480,108 +683,4 @@ interface RetryOptions {
480
683
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
481
684
  declare function sleep(ms: number): Promise<void>;
482
685
 
483
- interface FaceBoundingBox {
484
- originX: number;
485
- originY: number;
486
- width: number;
487
- height: number;
488
- }
489
- interface BlurAnalysis {
490
- variance: number;
491
- isBlurry: boolean;
492
- threshold: number;
493
- }
494
- interface LightingAnalysis {
495
- faceBrightness: number;
496
- backgroundBrightness: number;
497
- ratio: number;
498
- status: 'good' | 'backlit' | 'low_light';
499
- warning?: string;
500
- }
501
- interface FaceVisibilityResult {
502
- visible: boolean;
503
- reason?: string;
504
- }
505
- interface FaceAlignmentResult {
506
- score: number;
507
- tooClose: boolean;
508
- tooFar: boolean;
509
- }
510
- interface OvalRegion {
511
- centerX: number;
512
- centerY: number;
513
- width: number;
514
- height: number;
515
- }
516
- interface FaceInOvalResult {
517
- isInside: boolean;
518
- centerInOval: boolean;
519
- sizeMatch: boolean;
520
- feedback?: string;
521
- }
522
- interface FrameQualityResult {
523
- passed: boolean;
524
- blur?: BlurAnalysis;
525
- lighting?: LightingAnalysis;
526
- visibility?: FaceVisibilityResult;
527
- alignment?: FaceAlignmentResult;
528
- rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
529
- }
530
- declare const DEFAULT_BLUR_THRESHOLD = 100;
531
- declare const BLUR_THRESHOLD_MOBILE = 150;
532
- declare const BACKLIT_RATIO_THRESHOLD = 0.6;
533
- declare const LOW_LIGHT_THRESHOLD = 50;
534
- declare const MIN_FACE_TOP_MARGIN = 0.1;
535
- declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
536
- declare const MIN_FACE_SIDE_MARGIN = 0.05;
537
- declare const MIN_CAPTURE_ALIGNMENT = 0.6;
538
- declare const HIGH_ALIGNMENT = 0.85;
539
- declare const GOOD_ALIGNMENT = 0.5;
540
- declare const IDEAL_CROP_MULTIPLIER = 3.33;
541
- declare const MIN_CROP_MULTIPLIER = 1.5;
542
- declare const MAX_CROP_MULTIPLIER = 4;
543
- declare const FACE_CENTER_VERTICAL_OFFSET = 0.15;
544
- declare const MIN_FACE_RATIO = 0.03;
545
- declare const MAX_FACE_RATIO = 0.7;
546
- declare const FACE_CROP_OUTPUT_SIZE = 224;
547
- declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
548
- declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
549
- declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
550
- declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
551
- declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
552
- declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
553
- declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
554
- declare const DEFAULT_OVAL_REGION: OvalRegion;
555
- declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
556
- declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
557
- declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
558
- declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
559
- declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
560
- x: number;
561
- y: number;
562
- size: number;
563
- };
564
- declare function checkFrameQuality(options: {
565
- faceBox?: FaceBoundingBox;
566
- frameWidth: number;
567
- frameHeight: number;
568
- blurAnalysis?: BlurAnalysis;
569
- lightingAnalysis?: LightingAnalysis;
570
- minAlignment?: number;
571
- }): FrameQualityResult;
572
- declare class BaseFrameCollector {
573
- protected frames: CapturedFrame[];
574
- protected maxFrames: number;
575
- protected startTime: number;
576
- constructor(maxFrames?: number);
577
- setMaxFrames(max: number): void;
578
- addFrame(frame: CapturedFrame): void;
579
- getFrames(): CapturedFrame[];
580
- getCount(): number;
581
- isComplete(): boolean;
582
- reset(): void;
583
- getStartTime(): number;
584
- getNextIndex(): number;
585
- }
586
-
587
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STATUS_MESSAGES, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceInOvalResult, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LOW_LIGHT_THRESHOLD, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
686
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_FACE_DETECTION_TIERS, DEFAULT_GAZE_THRESHOLDS, DEFAULT_HAND_OCCLUSION_CONFIG, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STABILIZER_CONFIG, DEFAULT_STATUS_MESSAGES, type DetectionResult, type DetectionSummary, type DetectorConfig, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.d.ts CHANGED
@@ -204,6 +204,204 @@ interface LivenessCallbacks {
204
204
  onStateChange?: OnStateChangeCallback;
205
205
  }
206
206
 
207
+ interface FaceBoundingBox {
208
+ originX: number;
209
+ originY: number;
210
+ width: number;
211
+ height: number;
212
+ }
213
+ interface BlurAnalysis {
214
+ variance: number;
215
+ isBlurry: boolean;
216
+ threshold: number;
217
+ }
218
+ interface LightingAnalysis {
219
+ faceBrightness: number;
220
+ backgroundBrightness: number;
221
+ ratio: number;
222
+ status: 'good' | 'backlit' | 'low_light';
223
+ warning?: string;
224
+ }
225
+ interface FaceVisibilityResult {
226
+ visible: boolean;
227
+ reason?: string;
228
+ }
229
+ interface FaceAlignmentResult {
230
+ score: number;
231
+ tooClose: boolean;
232
+ tooFar: boolean;
233
+ }
234
+ interface OvalRegion {
235
+ centerX: number;
236
+ centerY: number;
237
+ width: number;
238
+ height: number;
239
+ }
240
+ interface FaceInOvalResult {
241
+ isInside: boolean;
242
+ centerInOval: boolean;
243
+ sizeMatch: boolean;
244
+ feedback?: string;
245
+ }
246
+ interface FrameQualityResult {
247
+ passed: boolean;
248
+ blur?: BlurAnalysis;
249
+ lighting?: LightingAnalysis;
250
+ visibility?: FaceVisibilityResult;
251
+ alignment?: FaceAlignmentResult;
252
+ rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
253
+ }
254
+ declare const DEFAULT_BLUR_THRESHOLD = 100;
255
+ declare const BLUR_THRESHOLD_MOBILE = 150;
256
+ declare const BACKLIT_RATIO_THRESHOLD = 0.6;
257
+ declare const LOW_LIGHT_THRESHOLD = 50;
258
+ declare const MIN_FACE_TOP_MARGIN = 0.1;
259
+ declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
260
+ declare const MIN_FACE_SIDE_MARGIN = 0.05;
261
+ declare const MIN_CAPTURE_ALIGNMENT = 0.6;
262
+ declare const HIGH_ALIGNMENT = 0.85;
263
+ declare const GOOD_ALIGNMENT = 0.5;
264
+ declare const IDEAL_CROP_MULTIPLIER = 2;
265
+ declare const MIN_CROP_MULTIPLIER = 1.8;
266
+ declare const MAX_CROP_MULTIPLIER = 2.5;
267
+ declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
268
+ declare const MIN_FACE_RATIO = 0.036;
269
+ declare const MAX_FACE_RATIO = 0.7;
270
+ declare const FACE_CROP_OUTPUT_SIZE = 224;
271
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
272
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
273
+ declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
274
+ declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
275
+ declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
276
+ declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
277
+ declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
278
+ declare const DEFAULT_OVAL_REGION: OvalRegion;
279
+ declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
280
+ declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
281
+ declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
282
+ declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
283
+ declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
284
+ x: number;
285
+ y: number;
286
+ size: number;
287
+ };
288
+ declare function checkFrameQuality(options: {
289
+ faceBox?: FaceBoundingBox;
290
+ frameWidth: number;
291
+ frameHeight: number;
292
+ blurAnalysis?: BlurAnalysis;
293
+ lightingAnalysis?: LightingAnalysis;
294
+ minAlignment?: number;
295
+ }): FrameQualityResult;
296
+ declare class BaseFrameCollector {
297
+ protected frames: CapturedFrame[];
298
+ protected maxFrames: number;
299
+ protected startTime: number;
300
+ constructor(maxFrames?: number);
301
+ setMaxFrames(max: number): void;
302
+ addFrame(frame: CapturedFrame): void;
303
+ getFrames(): CapturedFrame[];
304
+ getCount(): number;
305
+ isComplete(): boolean;
306
+ reset(): void;
307
+ getStartTime(): number;
308
+ getNextIndex(): number;
309
+ }
310
+
311
+ interface FaceLandmarkPoint {
312
+ x: number;
313
+ y: number;
314
+ z: number;
315
+ }
316
+ interface LandmarkValidationResult {
317
+ valid: boolean;
318
+ message?: string;
319
+ }
320
+ declare const LANDMARK_INDEX: {
321
+ readonly NOSE_TIP: 1;
322
+ readonly UPPER_LIP: 13;
323
+ readonly LOWER_LIP: 14;
324
+ };
325
+ declare const LANDMARK_MIN_BOUND = 0.1;
326
+ declare const LANDMARK_MAX_BOUND = 0.9;
327
+ declare const MIN_LANDMARK_COUNT = 15;
328
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
329
+
330
+ interface DetectionResult {
331
+ type: string;
332
+ passed: boolean;
333
+ confidence: number;
334
+ message?: string;
335
+ metadata?: Record<string, unknown>;
336
+ }
337
+ interface DetectorConfig {
338
+ enabled: boolean;
339
+ minConfidence?: number;
340
+ }
341
+ interface DetectionSummary {
342
+ allPassed: boolean;
343
+ results: Map<string, DetectionResult>;
344
+ faceBox?: FaceBoundingBox;
345
+ faceLandmarks?: FaceLandmarkPoint[];
346
+ warnings: string[];
347
+ }
348
+ interface HeadPose {
349
+ yawDeg: number;
350
+ pitchDeg: number;
351
+ rollDeg: number;
352
+ }
353
+ interface GazeThresholds {
354
+ maxYaw: number;
355
+ maxPitch: number;
356
+ }
357
+ declare const DEFAULT_GAZE_THRESHOLDS: GazeThresholds;
358
+ interface HandOcclusionConfig {
359
+ faceExpansionFactor: number;
360
+ minLandmarksForOcclusion: number;
361
+ faceBoxExpiryMs: number;
362
+ centerRegion: number;
363
+ }
364
+ declare const DEFAULT_HAND_OCCLUSION_CONFIG: HandOcclusionConfig;
365
+ interface FaceDetectionTiers {
366
+ primaryConfidence: number;
367
+ secondaryConfidence: number;
368
+ }
369
+ declare const DEFAULT_FACE_DETECTION_TIERS: FaceDetectionTiers;
370
+ interface VideoFrameMetadata {
371
+ presentationTime: number;
372
+ expectedDisplayTime: number;
373
+ width: number;
374
+ height: number;
375
+ mediaTime: number;
376
+ presentedFrames: number;
377
+ processingDuration?: number;
378
+ }
379
+ interface StabilizationProgress {
380
+ elapsed: number;
381
+ stableFrameCount: number;
382
+ requiredFrames: number;
383
+ greenMean: number;
384
+ greenDelta: number | null;
385
+ threshold: number;
386
+ isStable: boolean;
387
+ progress: number;
388
+ }
389
+ interface StabilizationResult {
390
+ success: boolean;
391
+ elapsed: number;
392
+ finalGreenMean: number;
393
+ stableFrameCount: number;
394
+ message: string;
395
+ }
396
+ interface StabilizerConfig {
397
+ stabilityThreshold: number;
398
+ requiredStableFrames: number;
399
+ maxWaitMs: number;
400
+ checkIntervalMs: number;
401
+ sampleSize: number;
402
+ }
403
+ declare const DEFAULT_STABILIZER_CONFIG: StabilizerConfig;
404
+
207
405
  interface LivenessClientConfig {
208
406
  baseUrl?: string;
209
407
  apiKey: string;
@@ -244,7 +442,12 @@ declare class LivenessClient {
244
442
  model?: FastCheckModel;
245
443
  source?: FrameSource;
246
444
  }): Promise<LivenessResult>;
247
- private sendStreamFrame;
445
+ streamFrame(frame: CapturedFrame, options: {
446
+ sessionId: string;
447
+ model?: FastCheckModel;
448
+ source?: FrameSource;
449
+ }): Promise<FastCheckStreamResponse>;
450
+ private sendStreamFrameInternal;
248
451
  fastCheckStream(frames: CapturedFrame[], options?: {
249
452
  sessionId?: string;
250
453
  model?: FastCheckModel;
@@ -480,108 +683,4 @@ interface RetryOptions {
480
683
  declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
481
684
  declare function sleep(ms: number): Promise<void>;
482
685
 
483
- interface FaceBoundingBox {
484
- originX: number;
485
- originY: number;
486
- width: number;
487
- height: number;
488
- }
489
- interface BlurAnalysis {
490
- variance: number;
491
- isBlurry: boolean;
492
- threshold: number;
493
- }
494
- interface LightingAnalysis {
495
- faceBrightness: number;
496
- backgroundBrightness: number;
497
- ratio: number;
498
- status: 'good' | 'backlit' | 'low_light';
499
- warning?: string;
500
- }
501
- interface FaceVisibilityResult {
502
- visible: boolean;
503
- reason?: string;
504
- }
505
- interface FaceAlignmentResult {
506
- score: number;
507
- tooClose: boolean;
508
- tooFar: boolean;
509
- }
510
- interface OvalRegion {
511
- centerX: number;
512
- centerY: number;
513
- width: number;
514
- height: number;
515
- }
516
- interface FaceInOvalResult {
517
- isInside: boolean;
518
- centerInOval: boolean;
519
- sizeMatch: boolean;
520
- feedback?: string;
521
- }
522
- interface FrameQualityResult {
523
- passed: boolean;
524
- blur?: BlurAnalysis;
525
- lighting?: LightingAnalysis;
526
- visibility?: FaceVisibilityResult;
527
- alignment?: FaceAlignmentResult;
528
- rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
529
- }
530
- declare const DEFAULT_BLUR_THRESHOLD = 100;
531
- declare const BLUR_THRESHOLD_MOBILE = 150;
532
- declare const BACKLIT_RATIO_THRESHOLD = 0.6;
533
- declare const LOW_LIGHT_THRESHOLD = 50;
534
- declare const MIN_FACE_TOP_MARGIN = 0.1;
535
- declare const MIN_FACE_BOTTOM_MARGIN = 0.08;
536
- declare const MIN_FACE_SIDE_MARGIN = 0.05;
537
- declare const MIN_CAPTURE_ALIGNMENT = 0.6;
538
- declare const HIGH_ALIGNMENT = 0.85;
539
- declare const GOOD_ALIGNMENT = 0.5;
540
- declare const IDEAL_CROP_MULTIPLIER = 3.33;
541
- declare const MIN_CROP_MULTIPLIER = 1.5;
542
- declare const MAX_CROP_MULTIPLIER = 4;
543
- declare const FACE_CENTER_VERTICAL_OFFSET = 0.15;
544
- declare const MIN_FACE_RATIO = 0.03;
545
- declare const MAX_FACE_RATIO = 0.7;
546
- declare const FACE_CROP_OUTPUT_SIZE = 224;
547
- declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
548
- declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
549
- declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
550
- declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
551
- declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
552
- declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
553
- declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
554
- declare const DEFAULT_OVAL_REGION: OvalRegion;
555
- declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
556
- declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
557
- declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
558
- declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
559
- declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
560
- x: number;
561
- y: number;
562
- size: number;
563
- };
564
- declare function checkFrameQuality(options: {
565
- faceBox?: FaceBoundingBox;
566
- frameWidth: number;
567
- frameHeight: number;
568
- blurAnalysis?: BlurAnalysis;
569
- lightingAnalysis?: LightingAnalysis;
570
- minAlignment?: number;
571
- }): FrameQualityResult;
572
- declare class BaseFrameCollector {
573
- protected frames: CapturedFrame[];
574
- protected maxFrames: number;
575
- protected startTime: number;
576
- constructor(maxFrames?: number);
577
- setMaxFrames(max: number): void;
578
- addFrame(frame: CapturedFrame): void;
579
- getFrames(): CapturedFrame[];
580
- getCount(): number;
581
- isComplete(): boolean;
582
- reset(): void;
583
- getStartTime(): number;
584
- getNextIndex(): number;
585
- }
586
-
587
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STATUS_MESSAGES, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceInOvalResult, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LOW_LIGHT_THRESHOLD, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
686
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_PATHS, AUTH_CONFIG, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_ENDPOINT, DEFAULT_FACE_DETECTION_TIERS, DEFAULT_GAZE_THRESHOLDS, DEFAULT_HAND_OCCLUSION_CONFIG, DEFAULT_LIVENESS_CONFIG, DEFAULT_LOCALE, DEFAULT_OVAL_REGION, DEFAULT_STABILIZER_CONFIG, DEFAULT_STATUS_MESSAGES, type DetectionResult, type DetectionSummary, type DetectorConfig, ES_LOCALE, type ErrorResponse, FACE_CENTER_VERTICAL_OFFSET, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FRAME_BUFFER_CONFIG, FRAME_CONFIG, type FaceAlignmentResult, type FaceBoundingBox, type FaceDetectionTiers, type FaceInOvalResult, type FaceLandmarkPoint, type FaceVisibilityResult, type FastCheckCropsRequest, type FastCheckModel, type FastCheckRequest, type FastCheckResponse, type FastCheckStreamRequest, type FastCheckStreamResponse, type FeedbackLocale, type FeedbackMessageKey, type Frame, FrameBuffer, type FrameData, type FrameQualityResult, FrameQueue, type FrameSource, GOOD_ALIGNMENT, type GazeThresholds, HIGH_ALIGNMENT, HYBRID_MODEL_CONFIGS, type HandOcclusionConfig, type HeadPose, type HealthResponse, type Hybrid150CheckRequest, type Hybrid50CheckRequest, type HybridCheckRequest, type HybridCheckResponse, type HybridFrameData, type HybridModelConfig, IDEAL_CROP_MULTIPLIER, type JobStatus, type JobStatusResponse, LANDMARK_INDEX, LANDMARK_MAX_BOUND, LANDMARK_MIN_BOUND, LOW_LIGHT_THRESHOLD, type LandmarkValidationResult, type LightingAnalysis, LivenessApiError, type LivenessCallbacks, LivenessClient, type LivenessClientConfig, type LivenessConfig, type LivenessResult, type LivenessState, MAX_CROP_MULTIPLIER, MAX_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelType, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, type OnErrorCallback, type OnFrameCapturedCallback, type OnProgressCallback, type OnResultCallback, type OnStateChangeCallback, type OvalGuideState, type OvalRegion, type QueueStatsResponse, RETRY_CONFIG, type RetryOptions, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkFrameQuality, decodeBase64, encodeBase64, generateSessionId, getCaptureQualityFeedback, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.js CHANGED
@@ -32,9 +32,13 @@ __export(index_exports, {
32
32
  BaseFrameCollector: () => BaseFrameCollector,
33
33
  DEFAULT_BLUR_THRESHOLD: () => DEFAULT_BLUR_THRESHOLD,
34
34
  DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
35
+ DEFAULT_FACE_DETECTION_TIERS: () => DEFAULT_FACE_DETECTION_TIERS,
36
+ DEFAULT_GAZE_THRESHOLDS: () => DEFAULT_GAZE_THRESHOLDS,
37
+ DEFAULT_HAND_OCCLUSION_CONFIG: () => DEFAULT_HAND_OCCLUSION_CONFIG,
35
38
  DEFAULT_LIVENESS_CONFIG: () => DEFAULT_LIVENESS_CONFIG,
36
39
  DEFAULT_LOCALE: () => DEFAULT_LOCALE,
37
40
  DEFAULT_OVAL_REGION: () => DEFAULT_OVAL_REGION,
41
+ DEFAULT_STABILIZER_CONFIG: () => DEFAULT_STABILIZER_CONFIG,
38
42
  DEFAULT_STATUS_MESSAGES: () => DEFAULT_STATUS_MESSAGES,
39
43
  ES_LOCALE: () => ES_LOCALE,
40
44
  FACE_CENTER_VERTICAL_OFFSET: () => FACE_CENTER_VERTICAL_OFFSET,
@@ -48,6 +52,9 @@ __export(index_exports, {
48
52
  HIGH_ALIGNMENT: () => HIGH_ALIGNMENT,
49
53
  HYBRID_MODEL_CONFIGS: () => HYBRID_MODEL_CONFIGS,
50
54
  IDEAL_CROP_MULTIPLIER: () => IDEAL_CROP_MULTIPLIER,
55
+ LANDMARK_INDEX: () => LANDMARK_INDEX,
56
+ LANDMARK_MAX_BOUND: () => LANDMARK_MAX_BOUND,
57
+ LANDMARK_MIN_BOUND: () => LANDMARK_MIN_BOUND,
51
58
  LOW_LIGHT_THRESHOLD: () => LOW_LIGHT_THRESHOLD,
52
59
  LivenessApiError: () => LivenessApiError,
53
60
  LivenessClient: () => LivenessClient,
@@ -60,6 +67,7 @@ __export(index_exports, {
60
67
  MIN_FACE_RATIO: () => MIN_FACE_RATIO,
61
68
  MIN_FACE_SIDE_MARGIN: () => MIN_FACE_SIDE_MARGIN,
62
69
  MIN_FACE_TOP_MARGIN: () => MIN_FACE_TOP_MARGIN,
70
+ MIN_LANDMARK_COUNT: () => MIN_LANDMARK_COUNT,
63
71
  MODEL_CONFIGS: () => MODEL_CONFIGS,
64
72
  OVAL_GUIDE_COLORS: () => OVAL_GUIDE_COLORS,
65
73
  OVAL_GUIDE_STYLES: () => OVAL_GUIDE_STYLES,
@@ -93,6 +101,7 @@ __export(index_exports, {
93
101
  toLivenessResult: () => toLivenessResult,
94
102
  toLivenessResultFromStream: () => toLivenessResultFromStream,
95
103
  validateApiKey: () => validateApiKey,
104
+ validateFaceLandmarks: () => validateFaceLandmarks,
96
105
  validateFrameCount: () => validateFrameCount,
97
106
  validateFrameData: () => validateFrameData,
98
107
  validateFrameIndex: () => validateFrameIndex,
@@ -385,18 +394,47 @@ var LivenessClient = class {
385
394
  return toLivenessResult(response);
386
395
  }
387
396
  /**
388
- * Send a single frame to the streaming endpoint with retry
397
+ * Send a single captured frame to the streaming endpoint.
398
+ *
399
+ * Use this to implement **real-time** streaming: call once per captured
400
+ * frame instead of batching all frames first.
401
+ *
402
+ * - While the server is still collecting frames the response will have
403
+ * `status: 'buffering'` with `frames_received` / `frames_required`.
404
+ * - When the last required frame arrives the response will have
405
+ * `status: 'complete'` with the full liveness result.
406
+ *
407
+ * Each frame gets its own retry via {@link retryWithBackoff}.
408
+ *
409
+ * @param frame - A single captured frame
410
+ * @param options - Session, model and source
411
+ * @returns Stream response (buffering or complete)
412
+ */
413
+ async streamFrame(frame, options) {
414
+ const frameData = {
415
+ index: frame.index,
416
+ timestamp_ms: frame.timestampMs,
417
+ pixels: frame.pixels
418
+ };
419
+ return this.sendStreamFrameInternal(frameData, {
420
+ sessionId: options.sessionId,
421
+ model: options.model ?? "10",
422
+ source: options.source ?? "live"
423
+ });
424
+ }
425
+ /**
426
+ * Send a single FrameData to the streaming endpoint with retry (internal)
389
427
  *
390
428
  * @param frame - Single frame to send
391
429
  * @param options - Session and model options
392
430
  * @returns Stream response with status
393
431
  */
394
- async sendStreamFrame(frame, options) {
432
+ async sendStreamFrameInternal(frameData, options) {
395
433
  const request = {
396
434
  session_id: options.sessionId,
397
435
  model: options.model,
398
436
  source: options.source,
399
- frame
437
+ frame: frameData
400
438
  };
401
439
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
402
440
  method: "POST",
@@ -422,7 +460,7 @@ var LivenessClient = class {
422
460
  const total = frameDataList.length;
423
461
  let finalResponse = null;
424
462
  const sendPromises = frameDataList.map(async (frameData, index) => {
425
- const response = await this.sendStreamFrame(frameData, {
463
+ const response = await this.sendStreamFrameInternal(frameData, {
426
464
  sessionId,
427
465
  model,
428
466
  source
@@ -471,7 +509,7 @@ var LivenessClient = class {
471
509
  const total = frameDataList.length;
472
510
  let finalResponse = null;
473
511
  for (const frameData of frameDataList) {
474
- const response = await this.sendStreamFrame(frameData, {
512
+ const response = await this.sendStreamFrameInternal(frameData, {
475
513
  sessionId,
476
514
  model,
477
515
  source
@@ -891,6 +929,29 @@ function hasEnoughFrames(model, frameCount) {
891
929
  return frameCount >= MODEL_CONFIGS[model].minFrames;
892
930
  }
893
931
 
932
+ // src/types/detectors.ts
933
+ var DEFAULT_GAZE_THRESHOLDS = {
934
+ maxYaw: 25,
935
+ maxPitch: 20
936
+ };
937
+ var DEFAULT_HAND_OCCLUSION_CONFIG = {
938
+ faceExpansionFactor: 0.5,
939
+ minLandmarksForOcclusion: 3,
940
+ faceBoxExpiryMs: 1e3,
941
+ centerRegion: 0.6
942
+ };
943
+ var DEFAULT_FACE_DETECTION_TIERS = {
944
+ primaryConfidence: 0.7,
945
+ secondaryConfidence: 0.4
946
+ };
947
+ var DEFAULT_STABILIZER_CONFIG = {
948
+ stabilityThreshold: 1.5,
949
+ requiredStableFrames: 15,
950
+ maxWaitMs: 4e3,
951
+ checkIntervalMs: 100,
952
+ sampleSize: 64
953
+ };
954
+
894
955
  // src/constants/feedback.ts
895
956
  var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
896
957
  var ALIGNMENT_THRESHOLD_POOR = 0.5;
@@ -1142,15 +1203,15 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1142
1203
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1143
1204
  var HIGH_ALIGNMENT = 0.85;
1144
1205
  var GOOD_ALIGNMENT = 0.5;
1145
- var IDEAL_CROP_MULTIPLIER = 3.33;
1146
- var MIN_CROP_MULTIPLIER = 1.5;
1147
- var MAX_CROP_MULTIPLIER = 4;
1148
- var FACE_CENTER_VERTICAL_OFFSET = 0.15;
1149
- var MIN_FACE_RATIO = 0.03;
1206
+ var IDEAL_CROP_MULTIPLIER = 2;
1207
+ var MIN_CROP_MULTIPLIER = 1.8;
1208
+ var MAX_CROP_MULTIPLIER = 2.5;
1209
+ var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1210
+ var MIN_FACE_RATIO = 0.036;
1150
1211
  var MAX_FACE_RATIO = 0.7;
1151
1212
  var FACE_CROP_OUTPUT_SIZE = 224;
1152
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
1153
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
1213
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1214
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1154
1215
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1155
1216
  const laplacian = [];
1156
1217
  for (let y = 1; y < height - 1; y++) {
@@ -1239,10 +1300,10 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
1239
1300
  var DEFAULT_OVAL_REGION = {
1240
1301
  centerX: 0.5,
1241
1302
  centerY: 0.5,
1242
- width: 0.3,
1243
- // 30% of frame width
1244
- height: 0.4
1245
- // 30% * (4/3) = 40% of frame height (3:4 aspect ratio)
1303
+ width: 0.36,
1304
+ // 36% of frame width (+20%)
1305
+ height: 0.48
1306
+ // 36% * (4/3) = 48% of frame height (+20%)
1246
1307
  };
1247
1308
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
1248
1309
  const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
@@ -1431,6 +1492,37 @@ var BaseFrameCollector = class {
1431
1492
  return this.frames.length;
1432
1493
  }
1433
1494
  };
1495
+
1496
+ // src/utils/landmarkValidator.ts
1497
+ var LANDMARK_INDEX = {
1498
+ /** Nose tip */
1499
+ NOSE_TIP: 1,
1500
+ /** Upper lip center */
1501
+ UPPER_LIP: 13,
1502
+ /** Lower lip center */
1503
+ LOWER_LIP: 14
1504
+ };
1505
+ var LANDMARK_MIN_BOUND = 0.1;
1506
+ var LANDMARK_MAX_BOUND = 0.9;
1507
+ var MIN_LANDMARK_COUNT = 15;
1508
+ function isInBounds(x, y) {
1509
+ return x >= LANDMARK_MIN_BOUND && x <= LANDMARK_MAX_BOUND && y >= LANDMARK_MIN_BOUND && y <= LANDMARK_MAX_BOUND;
1510
+ }
1511
+ function validateFaceLandmarks(landmarks) {
1512
+ if (!landmarks || landmarks.length < MIN_LANDMARK_COUNT) {
1513
+ return { valid: false, message: "Move closer to the camera" };
1514
+ }
1515
+ const noseTip = landmarks[LANDMARK_INDEX.NOSE_TIP];
1516
+ const upperLip = landmarks[LANDMARK_INDEX.UPPER_LIP];
1517
+ const lowerLip = landmarks[LANDMARK_INDEX.LOWER_LIP];
1518
+ if (!noseTip || !isInBounds(noseTip.x, noseTip.y)) {
1519
+ return { valid: false, message: "Make sure your full face is visible" };
1520
+ }
1521
+ if (!upperLip || !lowerLip || !isInBounds(upperLip.x, upperLip.y) || !isInBounds(lowerLip.x, lowerLip.y)) {
1522
+ return { valid: false, message: "Don't cover your mouth" };
1523
+ }
1524
+ return { valid: true };
1525
+ }
1434
1526
  // Annotate the CommonJS export names for ESM import in node:
1435
1527
  0 && (module.exports = {
1436
1528
  ALIGNMENT_THRESHOLD_CAPTURE,
@@ -1445,9 +1537,13 @@ var BaseFrameCollector = class {
1445
1537
  BaseFrameCollector,
1446
1538
  DEFAULT_BLUR_THRESHOLD,
1447
1539
  DEFAULT_ENDPOINT,
1540
+ DEFAULT_FACE_DETECTION_TIERS,
1541
+ DEFAULT_GAZE_THRESHOLDS,
1542
+ DEFAULT_HAND_OCCLUSION_CONFIG,
1448
1543
  DEFAULT_LIVENESS_CONFIG,
1449
1544
  DEFAULT_LOCALE,
1450
1545
  DEFAULT_OVAL_REGION,
1546
+ DEFAULT_STABILIZER_CONFIG,
1451
1547
  DEFAULT_STATUS_MESSAGES,
1452
1548
  ES_LOCALE,
1453
1549
  FACE_CENTER_VERTICAL_OFFSET,
@@ -1461,6 +1557,9 @@ var BaseFrameCollector = class {
1461
1557
  HIGH_ALIGNMENT,
1462
1558
  HYBRID_MODEL_CONFIGS,
1463
1559
  IDEAL_CROP_MULTIPLIER,
1560
+ LANDMARK_INDEX,
1561
+ LANDMARK_MAX_BOUND,
1562
+ LANDMARK_MIN_BOUND,
1464
1563
  LOW_LIGHT_THRESHOLD,
1465
1564
  LivenessApiError,
1466
1565
  LivenessClient,
@@ -1473,6 +1572,7 @@ var BaseFrameCollector = class {
1473
1572
  MIN_FACE_RATIO,
1474
1573
  MIN_FACE_SIDE_MARGIN,
1475
1574
  MIN_FACE_TOP_MARGIN,
1575
+ MIN_LANDMARK_COUNT,
1476
1576
  MODEL_CONFIGS,
1477
1577
  OVAL_GUIDE_COLORS,
1478
1578
  OVAL_GUIDE_STYLES,
@@ -1506,6 +1606,7 @@ var BaseFrameCollector = class {
1506
1606
  toLivenessResult,
1507
1607
  toLivenessResultFromStream,
1508
1608
  validateApiKey,
1609
+ validateFaceLandmarks,
1509
1610
  validateFrameCount,
1510
1611
  validateFrameData,
1511
1612
  validateFrameIndex,
package/dist/index.mjs CHANGED
@@ -281,18 +281,47 @@ var LivenessClient = class {
281
281
  return toLivenessResult(response);
282
282
  }
283
283
  /**
284
- * Send a single frame to the streaming endpoint with retry
284
+ * Send a single captured frame to the streaming endpoint.
285
+ *
286
+ * Use this to implement **real-time** streaming: call once per captured
287
+ * frame instead of batching all frames first.
288
+ *
289
+ * - While the server is still collecting frames the response will have
290
+ * `status: 'buffering'` with `frames_received` / `frames_required`.
291
+ * - When the last required frame arrives the response will have
292
+ * `status: 'complete'` with the full liveness result.
293
+ *
294
+ * Each frame gets its own retry via {@link retryWithBackoff}.
295
+ *
296
+ * @param frame - A single captured frame
297
+ * @param options - Session, model and source
298
+ * @returns Stream response (buffering or complete)
299
+ */
300
+ async streamFrame(frame, options) {
301
+ const frameData = {
302
+ index: frame.index,
303
+ timestamp_ms: frame.timestampMs,
304
+ pixels: frame.pixels
305
+ };
306
+ return this.sendStreamFrameInternal(frameData, {
307
+ sessionId: options.sessionId,
308
+ model: options.model ?? "10",
309
+ source: options.source ?? "live"
310
+ });
311
+ }
312
+ /**
313
+ * Send a single FrameData to the streaming endpoint with retry (internal)
285
314
  *
286
315
  * @param frame - Single frame to send
287
316
  * @param options - Session and model options
288
317
  * @returns Stream response with status
289
318
  */
290
- async sendStreamFrame(frame, options) {
319
+ async sendStreamFrameInternal(frameData, options) {
291
320
  const request = {
292
321
  session_id: options.sessionId,
293
322
  model: options.model,
294
323
  source: options.source,
295
- frame
324
+ frame: frameData
296
325
  };
297
326
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
298
327
  method: "POST",
@@ -318,7 +347,7 @@ var LivenessClient = class {
318
347
  const total = frameDataList.length;
319
348
  let finalResponse = null;
320
349
  const sendPromises = frameDataList.map(async (frameData, index) => {
321
- const response = await this.sendStreamFrame(frameData, {
350
+ const response = await this.sendStreamFrameInternal(frameData, {
322
351
  sessionId,
323
352
  model,
324
353
  source
@@ -367,7 +396,7 @@ var LivenessClient = class {
367
396
  const total = frameDataList.length;
368
397
  let finalResponse = null;
369
398
  for (const frameData of frameDataList) {
370
- const response = await this.sendStreamFrame(frameData, {
399
+ const response = await this.sendStreamFrameInternal(frameData, {
371
400
  sessionId,
372
401
  model,
373
402
  source
@@ -787,6 +816,29 @@ function hasEnoughFrames(model, frameCount) {
787
816
  return frameCount >= MODEL_CONFIGS[model].minFrames;
788
817
  }
789
818
 
819
+ // src/types/detectors.ts
820
+ var DEFAULT_GAZE_THRESHOLDS = {
821
+ maxYaw: 25,
822
+ maxPitch: 20
823
+ };
824
+ var DEFAULT_HAND_OCCLUSION_CONFIG = {
825
+ faceExpansionFactor: 0.5,
826
+ minLandmarksForOcclusion: 3,
827
+ faceBoxExpiryMs: 1e3,
828
+ centerRegion: 0.6
829
+ };
830
+ var DEFAULT_FACE_DETECTION_TIERS = {
831
+ primaryConfidence: 0.7,
832
+ secondaryConfidence: 0.4
833
+ };
834
+ var DEFAULT_STABILIZER_CONFIG = {
835
+ stabilityThreshold: 1.5,
836
+ requiredStableFrames: 15,
837
+ maxWaitMs: 4e3,
838
+ checkIntervalMs: 100,
839
+ sampleSize: 64
840
+ };
841
+
790
842
  // src/constants/feedback.ts
791
843
  var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
792
844
  var ALIGNMENT_THRESHOLD_POOR = 0.5;
@@ -1038,15 +1090,15 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1038
1090
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1039
1091
  var HIGH_ALIGNMENT = 0.85;
1040
1092
  var GOOD_ALIGNMENT = 0.5;
1041
- var IDEAL_CROP_MULTIPLIER = 3.33;
1042
- var MIN_CROP_MULTIPLIER = 1.5;
1043
- var MAX_CROP_MULTIPLIER = 4;
1044
- var FACE_CENTER_VERTICAL_OFFSET = 0.15;
1045
- var MIN_FACE_RATIO = 0.03;
1093
+ var IDEAL_CROP_MULTIPLIER = 2;
1094
+ var MIN_CROP_MULTIPLIER = 1.8;
1095
+ var MAX_CROP_MULTIPLIER = 2.5;
1096
+ var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1097
+ var MIN_FACE_RATIO = 0.036;
1046
1098
  var MAX_FACE_RATIO = 0.7;
1047
1099
  var FACE_CROP_OUTPUT_SIZE = 224;
1048
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.5;
1049
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.3;
1100
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1101
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1050
1102
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1051
1103
  const laplacian = [];
1052
1104
  for (let y = 1; y < height - 1; y++) {
@@ -1135,10 +1187,10 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
1135
1187
  var DEFAULT_OVAL_REGION = {
1136
1188
  centerX: 0.5,
1137
1189
  centerY: 0.5,
1138
- width: 0.3,
1139
- // 30% of frame width
1140
- height: 0.4
1141
- // 30% * (4/3) = 40% of frame height (3:4 aspect ratio)
1190
+ width: 0.36,
1191
+ // 36% of frame width (+20%)
1192
+ height: 0.48
1193
+ // 36% * (4/3) = 48% of frame height (+20%)
1142
1194
  };
1143
1195
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
1144
1196
  const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
@@ -1327,6 +1379,37 @@ var BaseFrameCollector = class {
1327
1379
  return this.frames.length;
1328
1380
  }
1329
1381
  };
1382
+
1383
+ // src/utils/landmarkValidator.ts
1384
+ var LANDMARK_INDEX = {
1385
+ /** Nose tip */
1386
+ NOSE_TIP: 1,
1387
+ /** Upper lip center */
1388
+ UPPER_LIP: 13,
1389
+ /** Lower lip center */
1390
+ LOWER_LIP: 14
1391
+ };
1392
+ var LANDMARK_MIN_BOUND = 0.1;
1393
+ var LANDMARK_MAX_BOUND = 0.9;
1394
+ var MIN_LANDMARK_COUNT = 15;
1395
+ function isInBounds(x, y) {
1396
+ return x >= LANDMARK_MIN_BOUND && x <= LANDMARK_MAX_BOUND && y >= LANDMARK_MIN_BOUND && y <= LANDMARK_MAX_BOUND;
1397
+ }
1398
+ function validateFaceLandmarks(landmarks) {
1399
+ if (!landmarks || landmarks.length < MIN_LANDMARK_COUNT) {
1400
+ return { valid: false, message: "Move closer to the camera" };
1401
+ }
1402
+ const noseTip = landmarks[LANDMARK_INDEX.NOSE_TIP];
1403
+ const upperLip = landmarks[LANDMARK_INDEX.UPPER_LIP];
1404
+ const lowerLip = landmarks[LANDMARK_INDEX.LOWER_LIP];
1405
+ if (!noseTip || !isInBounds(noseTip.x, noseTip.y)) {
1406
+ return { valid: false, message: "Make sure your full face is visible" };
1407
+ }
1408
+ if (!upperLip || !lowerLip || !isInBounds(upperLip.x, upperLip.y) || !isInBounds(lowerLip.x, lowerLip.y)) {
1409
+ return { valid: false, message: "Don't cover your mouth" };
1410
+ }
1411
+ return { valid: true };
1412
+ }
1330
1413
  export {
1331
1414
  ALIGNMENT_THRESHOLD_CAPTURE,
1332
1415
  ALIGNMENT_THRESHOLD_GOOD,
@@ -1340,9 +1423,13 @@ export {
1340
1423
  BaseFrameCollector,
1341
1424
  DEFAULT_BLUR_THRESHOLD,
1342
1425
  DEFAULT_ENDPOINT,
1426
+ DEFAULT_FACE_DETECTION_TIERS,
1427
+ DEFAULT_GAZE_THRESHOLDS,
1428
+ DEFAULT_HAND_OCCLUSION_CONFIG,
1343
1429
  DEFAULT_LIVENESS_CONFIG,
1344
1430
  DEFAULT_LOCALE,
1345
1431
  DEFAULT_OVAL_REGION,
1432
+ DEFAULT_STABILIZER_CONFIG,
1346
1433
  DEFAULT_STATUS_MESSAGES,
1347
1434
  ES_LOCALE,
1348
1435
  FACE_CENTER_VERTICAL_OFFSET,
@@ -1356,6 +1443,9 @@ export {
1356
1443
  HIGH_ALIGNMENT,
1357
1444
  HYBRID_MODEL_CONFIGS,
1358
1445
  IDEAL_CROP_MULTIPLIER,
1446
+ LANDMARK_INDEX,
1447
+ LANDMARK_MAX_BOUND,
1448
+ LANDMARK_MIN_BOUND,
1359
1449
  LOW_LIGHT_THRESHOLD,
1360
1450
  LivenessApiError,
1361
1451
  LivenessClient,
@@ -1368,6 +1458,7 @@ export {
1368
1458
  MIN_FACE_RATIO,
1369
1459
  MIN_FACE_SIDE_MARGIN,
1370
1460
  MIN_FACE_TOP_MARGIN,
1461
+ MIN_LANDMARK_COUNT,
1371
1462
  MODEL_CONFIGS,
1372
1463
  OVAL_GUIDE_COLORS,
1373
1464
  OVAL_GUIDE_STYLES,
@@ -1401,6 +1492,7 @@ export {
1401
1492
  toLivenessResult,
1402
1493
  toLivenessResultFromStream,
1403
1494
  validateApiKey,
1495
+ validateFaceLandmarks,
1404
1496
  validateFrameCount,
1405
1497
  validateFrameData,
1406
1498
  validateFrameIndex,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",