@moveris/shared 2.6.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,7 +26,7 @@ const client = new LivenessClient({
26
26
  // Perform a fast liveness check
27
27
  const result = await client.fastCheck(frames, {
28
28
  sessionId: 'my-session-id', // optional — auto-generated if omitted
29
- model: '10',
29
+ model: 'mixed-10-v2',
30
30
  source: 'live',
31
31
  });
32
32
 
@@ -64,18 +64,18 @@ Perform fast liveness check with server-side face detection. Ideal for quick ver
64
64
  ```typescript
65
65
  const result = await client.fastCheck(frames, {
66
66
  sessionId: 'optional-uuid',
67
- model: '10', // '10' | '50' | '250'
67
+ model: 'mixed-10-v2',
68
68
  source: 'live', // 'live' | 'media'
69
69
  });
70
70
  ```
71
71
 
72
72
  **Parameters:**
73
73
 
74
- | Option | Type | Default | Description |
75
- | ----------- | ---------------- | -------------- | ----------------------------------------------------------------------- |
76
- | `sessionId` | `string` | auto-generated | Unique session identifier (UUID) |
77
- | `model` | `FastCheckModel` | `'10'` | Model to use: `'10'` (fast), `'50'` (balanced), `'250'` (high-accuracy) |
78
- | `source` | `FrameSource` | `'live'` | Frame source: `'live'` (camera) or `'media'` (recorded video) |
74
+ | Option | Type | Default | Description |
75
+ | ----------- | ---------------- | --------------- | ------------------------------------------------------------- |
76
+ | `sessionId` | `string` | auto-generated | Unique session identifier (UUID) |
77
+ | `model` | `FastCheckModel` | `'mixed-10-v2'` | Model to use (see Models section) |
78
+ | `source` | `FrameSource` | `'live'` | Frame source: `'live'` (camera) or `'media'` (recorded video) |
79
79
 
80
80
  **Returns:** `Promise<LivenessResult>`
81
81
 
@@ -94,6 +94,7 @@ const crops: CropData[] = capturedFrames.map((f) => ({
94
94
  const result = await client.fastCheckCrops(crops, {
95
95
  model: '10',
96
96
  source: 'live',
97
+ bgSegmentation: true, // optional — sent as bg_segmentation in payload
97
98
  });
98
99
  ```
99
100
 
@@ -184,22 +185,34 @@ Model selection for liveness detection speed/accuracy trade-off.
184
185
 
185
186
  ```typescript
186
187
  type FastCheckModel =
188
+ | 'mixed-10-v2'
189
+ | 'mixed-30-v2'
190
+ | 'mixed-60-v2'
191
+ | 'mixed-90-v2'
192
+ | 'mixed-120-v2' // Active mixed-v2 models (recommended)
187
193
  | '10'
188
194
  | '50'
189
- | '250' // Standard models
190
- | 'hybrid-v2-10'
191
- | 'hybrid-v2-50' // Hybrid V2 (physiological features)
192
- | 'mixed-10'; // Mixed (visual + physiological scoring)
195
+ | '250' // Legacy standard models
196
+ | (string & object); // Forward-compatible with future model IDs
193
197
  ```
194
198
 
195
- | Value | Frames | Category | Description |
196
- | ---------------- | ------ | --------- | ----------------------------------------- |
197
- | `'10'` | 10 | Standard | Fast verification, lowest latency |
198
- | `'50'` | 50 | Standard | Balanced speed and accuracy |
199
- | `'250'` | 250 | Standard | Highest accuracy, slower |
200
- | `'hybrid-v2-10'` | 10 | Hybrid V2 | Visual + physiological feature extraction |
201
- | `'hybrid-v2-50'` | 50 | Hybrid V2 | Higher accuracy physiological features |
202
- | `'mixed-10'` | 10 | Mixed | Combined visual + physiological scoring |
199
+ Active models (recommended):
200
+
201
+ | Value | Frames | Description |
202
+ | ---------------- | ------ | ------------------------------ |
203
+ | `'mixed-10-v2'` | 10 | Fast verification, low latency |
204
+ | `'mixed-30-v2'` | 30 | Balanced speed and accuracy |
205
+ | `'mixed-60-v2'` | 60 | Higher accuracy |
206
+ | `'mixed-90-v2'` | 90 | High accuracy |
207
+ | `'mixed-120-v2'` | 120 | Highest accuracy, slower |
208
+
209
+ Legacy models (still supported):
210
+
211
+ | Value | Frames | Description |
212
+ | ------- | ------ | --------------------------- |
213
+ | `'10'` | 10 | Standard — fast |
214
+ | `'50'` | 50 | Standard — balanced |
215
+ | `'250'` | 250 | Standard — highest accuracy |
203
216
 
204
217
  #### FrameSource
205
218
 
@@ -431,6 +444,9 @@ const status = getStatusMessage('capturing', DEFAULT_LOCALE);
431
444
  | `eyes_overexposed` | "Eye region overexposed…" | Eye region too bright |
432
445
  | `glasses_glare` | "Glare detected…" | Specular highlights on eyes |
433
446
  | `eye_quality_poor` | "Eye region quality is poor" | Generic eye quality failure |
447
+ | `camera_angle_low` | "Raise camera to eye level" | Camera below face (Y < 0.3) |
448
+ | `camera_angle_high` | "Lower camera to eye level" | Camera above face (Y > 0.7) |
449
+ | `camera_tilted` | "Hold camera level" | Eye line deviates > 15° |
434
450
 
435
451
  ---
436
452
 
@@ -492,6 +508,8 @@ const glareRatio = detectSpecularHighlights(pixels);
492
508
  | Glare | `maxGlareRatio: 0.15` | >15% of pixels are specular highlights |
493
509
  | Occluded | `minContrast: 12` | Standard deviation of luminance too low |
494
510
 
511
+ Glare detection uses a **relative threshold** (`mean brightness × 2.5`) so sensitivity adapts to ambient lighting, avoiding false positives in bright environments.
512
+
495
513
  Custom thresholds can be passed as a second argument to `checkEyeRegionQuality()`.
496
514
 
497
515
  ### Eye Region Landmarks
@@ -515,7 +533,13 @@ if (bounds) {
515
533
  Utilities for assessing frame quality before submission.
516
534
 
517
535
  ```typescript
518
- import { isFaceFullyVisible, isFaceInOval, calculateFaceCropRegion } from '@moveris/shared';
536
+ import {
537
+ isFaceFullyVisible,
538
+ isFaceInOval,
539
+ calculateFaceCropRegion,
540
+ detectFaceRoll,
541
+ detectCameraAngle,
542
+ } from '@moveris/shared';
519
543
 
520
544
  // Check if face is fully visible in frame
521
545
  const visibility = isFaceFullyVisible(faceBbox, frameWidth, frameHeight);
@@ -533,8 +557,17 @@ const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
533
557
 
534
558
  // Calculate adaptive crop multiplier based on face size
535
559
  import { calculateAdaptiveCropMultiplier } from '@moveris/shared';
536
- const multiplier = calculateAdaptiveCropMultiplier(faceSize, videoSize);
537
- // Returns 1.82.5x (matched to cognito-check for ~50% face coverage)
560
+ const multiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
561
+ // Returns 2.54.0x (matched to cognito-check for ~33% face coverage in 224×224 crop)
562
+
563
+ // Detect face roll (device tilt) from eye-corner landmarks
564
+ const rollResult = detectFaceRoll(landmarks); // requires ≥364 landmarks
565
+ // { roll: number (degrees), tooTilted: boolean }
566
+
567
+ // Detect camera vertical angle from forehead/nose/chin landmark ratio
568
+ const angleResult = detectCameraAngle(landmarks); // requires ≥153 landmarks
569
+ // { ratio: number, cameraAbove: boolean, cameraBelow: boolean }
570
+ // ratio ~1.0 at eye level; >1.35 = camera above user; <0.75 = camera below user
538
571
  ```
539
572
 
540
573
  #### Crop Constants (aligned with cognito-check)
package/dist/index.d.mts CHANGED
@@ -29,6 +29,7 @@ interface FastCheckRequest {
29
29
  model?: FastCheckModel;
30
30
  source?: FrameSource;
31
31
  frames: FrameData[];
32
+ warnings?: string[];
32
33
  }
33
34
  interface FastCheckCropsRequest {
34
35
  session_id: string;
@@ -64,6 +65,7 @@ interface FastCheckStreamRequest {
64
65
  model?: FastCheckModel;
65
66
  source?: FrameSource;
66
67
  frame: FrameData;
68
+ warnings?: string[];
67
69
  }
68
70
  interface FastCheckResponse {
69
71
  verdict: Verdict | null;
@@ -76,6 +78,7 @@ interface FastCheckResponse {
76
78
  frames_processed: number;
77
79
  available: boolean;
78
80
  warning: string | null;
81
+ warnings?: string[] | null;
79
82
  error: string | null;
80
83
  }
81
84
  interface FastCheckStreamResponse {
@@ -93,6 +96,7 @@ interface FastCheckStreamResponse {
93
96
  frames_processed: number | null;
94
97
  available: boolean;
95
98
  warning: string | null;
99
+ warnings?: string[] | null;
96
100
  error: string | null;
97
101
  }
98
102
  interface VerifyResponse {
@@ -157,6 +161,7 @@ interface LivenessResult {
157
161
  sessionId: string;
158
162
  processingMs: number;
159
163
  framesProcessed: number;
164
+ warnings?: string[];
160
165
  }
161
166
  type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
162
167
  interface LivenessConfig {
@@ -296,7 +301,7 @@ interface FrameQualityResult {
296
301
  alignment?: FaceAlignmentResult;
297
302
  rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
298
303
  }
299
- declare const DEFAULT_BLUR_THRESHOLD = 100;
304
+ declare const DEFAULT_BLUR_THRESHOLD = 110;
300
305
  declare const BLUR_THRESHOLD_MOBILE = 60;
301
306
  declare const BACKLIT_RATIO_THRESHOLD = 0.6;
302
307
  declare const LOW_LIGHT_THRESHOLD = 50;
@@ -348,6 +353,14 @@ interface FaceRollResult {
348
353
  }
349
354
  declare const MAX_FACE_ROLL_DEGREES = 15;
350
355
  declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
356
+ interface CameraAngleResult {
357
+ ratio: number;
358
+ cameraAbove: boolean;
359
+ cameraBelow: boolean;
360
+ }
361
+ declare const CAMERA_ANGLE_HIGH_RATIO = 1.35;
362
+ declare const CAMERA_ANGLE_LOW_RATIO = 0.75;
363
+ declare function detectCameraAngle(landmarks: FaceLandmarkPoint[]): CameraAngleResult;
351
364
  declare class BaseFrameCollector {
352
365
  protected frames: CapturedFrame[];
353
366
  protected maxFrames: number;
@@ -477,22 +490,27 @@ declare class LivenessClient {
477
490
  sessionId?: string;
478
491
  model?: FastCheckModel;
479
492
  source?: FrameSource;
493
+ warnings?: string[];
480
494
  }): Promise<LivenessResult>;
481
495
  fastCheckCrops(crops: CropData[], options?: {
482
496
  sessionId?: string;
483
497
  model?: FastCheckModel;
484
498
  source?: FrameSource;
499
+ warnings?: string[];
500
+ bgSegmentation?: boolean;
485
501
  }): Promise<LivenessResult>;
486
502
  streamFrame(frame: CapturedFrame, options: {
487
503
  sessionId: string;
488
504
  model?: FastCheckModel;
489
505
  source?: FrameSource;
506
+ warnings?: string[];
490
507
  }): Promise<FastCheckStreamResponse>;
491
508
  private sendStreamFrameInternal;
492
509
  fastCheckStream(frames: CapturedFrame[], options?: {
493
510
  sessionId?: string;
494
511
  model?: FastCheckModel;
495
512
  source?: FrameSource;
513
+ warnings?: string[];
496
514
  }, callbacks?: {
497
515
  onFrameSent?: (index: number, total: number) => void;
498
516
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -501,6 +519,7 @@ declare class LivenessClient {
501
519
  sessionId?: string;
502
520
  model?: FastCheckModel;
503
521
  source?: FrameSource;
522
+ warnings?: string[];
504
523
  }, callbacks?: {
505
524
  onFrameSent?: (index: number, total: number) => void;
506
525
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -694,8 +713,9 @@ declare const FEEDBACK_MESSAGES: {
694
713
  readonly poor_lighting: "Improve lighting";
695
714
  readonly too_dark: "Low lighting - move to a brighter area";
696
715
  readonly backlit: "Backlit - try facing the light source";
697
- readonly phone_angle_low: "Raise your phone to eye level";
698
- readonly phone_tilted: "Hold your phone level";
716
+ readonly camera_angle_low: "Raise camera to eye level";
717
+ readonly camera_angle_high: "Lower camera to eye level";
718
+ readonly camera_tilted: "Hold camera level";
699
719
  readonly hand_detected: "Remove hand from face";
700
720
  readonly eyes_not_visible: "Eyes not clearly visible";
701
721
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -728,8 +748,9 @@ interface CaptureQualityState {
728
748
  targetFrames: number;
729
749
  tooFarFromIdeal?: boolean;
730
750
  tooCloseToIdeal?: boolean;
731
- phoneAngled?: boolean;
732
- phoneTilted?: boolean;
751
+ cameraAngleLow?: boolean;
752
+ cameraAngleHigh?: boolean;
753
+ cameraTilted?: boolean;
733
754
  }
734
755
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
735
756
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -771,13 +792,13 @@ interface EyeQualityThresholds {
771
792
  minBrightness: number;
772
793
  maxBrightness: number;
773
794
  minContrast: number;
774
- glarePixelThreshold: number;
795
+ glareRelativeFactor: number;
775
796
  maxGlareRatio: number;
776
797
  }
777
798
  declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
778
799
  declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
779
800
  declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
780
- declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
801
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
781
802
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
782
803
 
783
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
804
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectCameraAngle, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.d.ts CHANGED
@@ -29,6 +29,7 @@ interface FastCheckRequest {
29
29
  model?: FastCheckModel;
30
30
  source?: FrameSource;
31
31
  frames: FrameData[];
32
+ warnings?: string[];
32
33
  }
33
34
  interface FastCheckCropsRequest {
34
35
  session_id: string;
@@ -64,6 +65,7 @@ interface FastCheckStreamRequest {
64
65
  model?: FastCheckModel;
65
66
  source?: FrameSource;
66
67
  frame: FrameData;
68
+ warnings?: string[];
67
69
  }
68
70
  interface FastCheckResponse {
69
71
  verdict: Verdict | null;
@@ -76,6 +78,7 @@ interface FastCheckResponse {
76
78
  frames_processed: number;
77
79
  available: boolean;
78
80
  warning: string | null;
81
+ warnings?: string[] | null;
79
82
  error: string | null;
80
83
  }
81
84
  interface FastCheckStreamResponse {
@@ -93,6 +96,7 @@ interface FastCheckStreamResponse {
93
96
  frames_processed: number | null;
94
97
  available: boolean;
95
98
  warning: string | null;
99
+ warnings?: string[] | null;
96
100
  error: string | null;
97
101
  }
98
102
  interface VerifyResponse {
@@ -157,6 +161,7 @@ interface LivenessResult {
157
161
  sessionId: string;
158
162
  processingMs: number;
159
163
  framesProcessed: number;
164
+ warnings?: string[];
160
165
  }
161
166
  type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
162
167
  interface LivenessConfig {
@@ -296,7 +301,7 @@ interface FrameQualityResult {
296
301
  alignment?: FaceAlignmentResult;
297
302
  rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
298
303
  }
299
- declare const DEFAULT_BLUR_THRESHOLD = 100;
304
+ declare const DEFAULT_BLUR_THRESHOLD = 110;
300
305
  declare const BLUR_THRESHOLD_MOBILE = 60;
301
306
  declare const BACKLIT_RATIO_THRESHOLD = 0.6;
302
307
  declare const LOW_LIGHT_THRESHOLD = 50;
@@ -348,6 +353,14 @@ interface FaceRollResult {
348
353
  }
349
354
  declare const MAX_FACE_ROLL_DEGREES = 15;
350
355
  declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
356
+ interface CameraAngleResult {
357
+ ratio: number;
358
+ cameraAbove: boolean;
359
+ cameraBelow: boolean;
360
+ }
361
+ declare const CAMERA_ANGLE_HIGH_RATIO = 1.35;
362
+ declare const CAMERA_ANGLE_LOW_RATIO = 0.75;
363
+ declare function detectCameraAngle(landmarks: FaceLandmarkPoint[]): CameraAngleResult;
351
364
  declare class BaseFrameCollector {
352
365
  protected frames: CapturedFrame[];
353
366
  protected maxFrames: number;
@@ -477,22 +490,27 @@ declare class LivenessClient {
477
490
  sessionId?: string;
478
491
  model?: FastCheckModel;
479
492
  source?: FrameSource;
493
+ warnings?: string[];
480
494
  }): Promise<LivenessResult>;
481
495
  fastCheckCrops(crops: CropData[], options?: {
482
496
  sessionId?: string;
483
497
  model?: FastCheckModel;
484
498
  source?: FrameSource;
499
+ warnings?: string[];
500
+ bgSegmentation?: boolean;
485
501
  }): Promise<LivenessResult>;
486
502
  streamFrame(frame: CapturedFrame, options: {
487
503
  sessionId: string;
488
504
  model?: FastCheckModel;
489
505
  source?: FrameSource;
506
+ warnings?: string[];
490
507
  }): Promise<FastCheckStreamResponse>;
491
508
  private sendStreamFrameInternal;
492
509
  fastCheckStream(frames: CapturedFrame[], options?: {
493
510
  sessionId?: string;
494
511
  model?: FastCheckModel;
495
512
  source?: FrameSource;
513
+ warnings?: string[];
496
514
  }, callbacks?: {
497
515
  onFrameSent?: (index: number, total: number) => void;
498
516
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -501,6 +519,7 @@ declare class LivenessClient {
501
519
  sessionId?: string;
502
520
  model?: FastCheckModel;
503
521
  source?: FrameSource;
522
+ warnings?: string[];
504
523
  }, callbacks?: {
505
524
  onFrameSent?: (index: number, total: number) => void;
506
525
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -694,8 +713,9 @@ declare const FEEDBACK_MESSAGES: {
694
713
  readonly poor_lighting: "Improve lighting";
695
714
  readonly too_dark: "Low lighting - move to a brighter area";
696
715
  readonly backlit: "Backlit - try facing the light source";
697
- readonly phone_angle_low: "Raise your phone to eye level";
698
- readonly phone_tilted: "Hold your phone level";
716
+ readonly camera_angle_low: "Raise camera to eye level";
717
+ readonly camera_angle_high: "Lower camera to eye level";
718
+ readonly camera_tilted: "Hold camera level";
699
719
  readonly hand_detected: "Remove hand from face";
700
720
  readonly eyes_not_visible: "Eyes not clearly visible";
701
721
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -728,8 +748,9 @@ interface CaptureQualityState {
728
748
  targetFrames: number;
729
749
  tooFarFromIdeal?: boolean;
730
750
  tooCloseToIdeal?: boolean;
731
- phoneAngled?: boolean;
732
- phoneTilted?: boolean;
751
+ cameraAngleLow?: boolean;
752
+ cameraAngleHigh?: boolean;
753
+ cameraTilted?: boolean;
733
754
  }
734
755
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
735
756
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -771,13 +792,13 @@ interface EyeQualityThresholds {
771
792
  minBrightness: number;
772
793
  maxBrightness: number;
773
794
  minContrast: number;
774
- glarePixelThreshold: number;
795
+ glareRelativeFactor: number;
775
796
  maxGlareRatio: number;
776
797
  }
777
798
  declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
778
799
  declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
779
800
  declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
780
- declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
801
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
781
802
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
782
803
 
783
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
804
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, 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, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, 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 FaceRollResult, 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, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_CROP_MULTIPLIER, MIN_FACE_BOTTOM_MARGIN, MIN_FACE_RATIO, MIN_FACE_SIDE_MARGIN, MIN_FACE_TOP_MARGIN, MIN_IDEAL_FACE_RATIO, MIN_LANDMARK_COUNT, MODEL_CONFIGS, type ModelConfig, type ModelEntry, type ModelType, type ModelsResponse, OVAL_GUIDE_COLORS, OVAL_GUIDE_STYLES, OVAL_REGION_DESKTOP, OVAL_REGION_MOBILE, 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, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, decodeBase64, detectCameraAngle, detectFaceRoll, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, retryWithBackoff, rgbaToGrayscale, sleep, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ __export(index_exports, {
31
31
  BACKLIT_RATIO_THRESHOLD: () => BACKLIT_RATIO_THRESHOLD,
32
32
  BLUR_THRESHOLD_MOBILE: () => BLUR_THRESHOLD_MOBILE,
33
33
  BaseFrameCollector: () => BaseFrameCollector,
34
+ CAMERA_ANGLE_HIGH_RATIO: () => CAMERA_ANGLE_HIGH_RATIO,
35
+ CAMERA_ANGLE_LOW_RATIO: () => CAMERA_ANGLE_LOW_RATIO,
34
36
  DEFAULT_BLUR_THRESHOLD: () => DEFAULT_BLUR_THRESHOLD,
35
37
  DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
36
38
  DEFAULT_FACE_DETECTION_TIERS: () => DEFAULT_FACE_DETECTION_TIERS,
@@ -95,6 +97,7 @@ __export(index_exports, {
95
97
  checkEyeRegionQuality: () => checkEyeRegionQuality,
96
98
  checkFrameQuality: () => checkFrameQuality,
97
99
  decodeBase64: () => decodeBase64,
100
+ detectCameraAngle: () => detectCameraAngle,
98
101
  detectFaceRoll: () => detectFaceRoll,
99
102
  detectSpecularHighlights: () => detectSpecularHighlights,
100
103
  encodeBase64: () => encodeBase64,
@@ -261,7 +264,8 @@ function toLivenessResult(response) {
261
264
  score: response.score ?? 0,
262
265
  sessionId: response.session_id,
263
266
  processingMs: response.processing_ms,
264
- framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
267
+ framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
268
+ warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
265
269
  };
266
270
  }
267
271
  function toLivenessResultFromStream(response) {
@@ -278,7 +282,8 @@ function toLivenessResultFromStream(response) {
278
282
  score: response.score ?? 0,
279
283
  sessionId: response.session_id,
280
284
  processingMs: response.processing_ms ?? 0,
281
- framesProcessed: response.frames_processed ?? 0
285
+ framesProcessed: response.frames_processed ?? 0,
286
+ warnings: Array.isArray(response.warnings) ? response.warnings : void 0
282
287
  };
283
288
  }
284
289
  function generateSessionId() {
@@ -457,7 +462,8 @@ var LivenessClient = class _LivenessClient {
457
462
  session_id: options.sessionId ?? generateSessionId(),
458
463
  model: options.model ?? "10",
459
464
  source: options.source ?? "live",
460
- frames: toFrameData(frames)
465
+ frames: toFrameData(frames),
466
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
461
467
  };
462
468
  const response = await this.requestWithRetry(API_PATHS.fastCheck, {
463
469
  method: "POST",
@@ -477,7 +483,9 @@ var LivenessClient = class _LivenessClient {
477
483
  session_id: options.sessionId ?? generateSessionId(),
478
484
  model: options.model ?? "10",
479
485
  source: options.source ?? "live",
480
- crops
486
+ crops,
487
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
488
+ ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
481
489
  };
482
490
  const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
483
491
  method: "POST",
@@ -511,7 +519,8 @@ var LivenessClient = class _LivenessClient {
511
519
  return this.sendStreamFrameInternal(frameData, {
512
520
  sessionId: options.sessionId,
513
521
  model: options.model ?? "10",
514
- source: options.source ?? "live"
522
+ source: options.source ?? "live",
523
+ warnings: options.warnings
515
524
  });
516
525
  }
517
526
  /**
@@ -526,7 +535,8 @@ var LivenessClient = class _LivenessClient {
526
535
  session_id: options.sessionId,
527
536
  model: options.model,
528
537
  source: options.source,
529
- frame: frameData
538
+ frame: frameData,
539
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
530
540
  };
531
541
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
532
542
  method: "POST",
@@ -555,7 +565,8 @@ var LivenessClient = class _LivenessClient {
555
565
  const response = await this.sendStreamFrameInternal(frameData, {
556
566
  sessionId,
557
567
  model,
558
- source
568
+ source,
569
+ warnings: options.warnings
559
570
  });
560
571
  callbacks?.onFrameSent?.(index + 1, total);
561
572
  if (response.status === "buffering") {
@@ -604,7 +615,8 @@ var LivenessClient = class _LivenessClient {
604
615
  const response = await this.sendStreamFrameInternal(frameData, {
605
616
  sessionId,
606
617
  model,
607
- source
618
+ source,
619
+ warnings: options.warnings
608
620
  });
609
621
  const currentIndex = frameData.index;
610
622
  callbacks?.onFrameSent?.(currentIndex + 1, total);
@@ -1246,9 +1258,10 @@ var FEEDBACK_MESSAGES = {
1246
1258
  poor_lighting: "Improve lighting",
1247
1259
  too_dark: "Low lighting - move to a brighter area",
1248
1260
  backlit: "Backlit - try facing the light source",
1249
- // Phone orientation
1250
- phone_angle_low: "Raise your phone to eye level",
1251
- phone_tilted: "Hold your phone level",
1261
+ // Camera angle (platform-agnostic)
1262
+ camera_angle_low: "Raise camera to eye level",
1263
+ camera_angle_high: "Lower camera to eye level",
1264
+ camera_tilted: "Hold camera level",
1252
1265
  // Hand occlusion
1253
1266
  hand_detected: "Remove hand from face",
1254
1267
  // Eye region quality
@@ -1308,9 +1321,10 @@ var ES_LOCALE = {
1308
1321
  poor_lighting: "Mejora la iluminaci\xF3n",
1309
1322
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1310
1323
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1311
- // Phone orientation
1312
- phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
1313
- phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
1324
+ // Camera angle (platform-agnostic)
1325
+ camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
1326
+ camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
1327
+ camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
1314
1328
  // Hand occlusion
1315
1329
  hand_detected: "Retira la mano del rostro",
1316
1330
  // Eye region quality
@@ -1346,17 +1360,21 @@ function getCaptureQualityFeedback(state) {
1346
1360
  targetFrames,
1347
1361
  tooFarFromIdeal,
1348
1362
  tooCloseToIdeal,
1349
- phoneAngled,
1350
- phoneTilted
1363
+ cameraAngleLow,
1364
+ cameraAngleHigh,
1365
+ cameraTilted
1351
1366
  } = state;
1352
1367
  if (!hasFace) {
1353
1368
  return FEEDBACK_MESSAGES.no_face;
1354
1369
  }
1355
- if (phoneTilted) {
1356
- return FEEDBACK_MESSAGES.phone_tilted;
1370
+ if (cameraTilted) {
1371
+ return FEEDBACK_MESSAGES.camera_tilted;
1357
1372
  }
1358
- if (phoneAngled) {
1359
- return FEEDBACK_MESSAGES.phone_angle_low;
1373
+ if (cameraAngleLow) {
1374
+ return FEEDBACK_MESSAGES.camera_angle_low;
1375
+ }
1376
+ if (cameraAngleHigh) {
1377
+ return FEEDBACK_MESSAGES.camera_angle_high;
1360
1378
  }
1361
1379
  if (tooClose) {
1362
1380
  return FEEDBACK_MESSAGES.too_close;
@@ -1400,10 +1418,11 @@ function canCaptureFrame(state) {
1400
1418
  isPartialFace,
1401
1419
  tooFarFromIdeal,
1402
1420
  tooCloseToIdeal,
1403
- phoneAngled,
1404
- phoneTilted
1421
+ cameraAngleLow,
1422
+ cameraAngleHigh,
1423
+ cameraTilted
1405
1424
  } = state;
1406
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1425
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
1407
1426
  }
1408
1427
 
1409
1428
  // src/utils/validators.ts
@@ -1470,7 +1489,7 @@ function decodeBase64(base64) {
1470
1489
  }
1471
1490
 
1472
1491
  // src/utils/frameAnalysis.ts
1473
- var DEFAULT_BLUR_THRESHOLD = 100;
1492
+ var DEFAULT_BLUR_THRESHOLD = 110;
1474
1493
  var BLUR_THRESHOLD_MOBILE = 60;
1475
1494
  var BACKLIT_RATIO_THRESHOLD = 0.6;
1476
1495
  var LOW_LIGHT_THRESHOLD = 50;
@@ -1771,6 +1790,30 @@ function detectFaceRoll(landmarks) {
1771
1790
  tooTilted: roll > MAX_FACE_ROLL_DEGREES
1772
1791
  };
1773
1792
  }
1793
+ var CAMERA_ANGLE_HIGH_RATIO = 1.35;
1794
+ var CAMERA_ANGLE_LOW_RATIO = 0.75;
1795
+ function detectCameraAngle(landmarks) {
1796
+ if (landmarks.length < 153) {
1797
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1798
+ }
1799
+ const forehead = landmarks[10];
1800
+ const noseTip = landmarks[1];
1801
+ const chin = landmarks[152];
1802
+ if (!forehead || !noseTip || !chin) {
1803
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1804
+ }
1805
+ const foreheadToNose = Math.abs(forehead.y - noseTip.y);
1806
+ const noseToChin = Math.abs(noseTip.y - chin.y);
1807
+ if (noseToChin < 1e-3) {
1808
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1809
+ }
1810
+ const ratio = foreheadToNose / noseToChin;
1811
+ return {
1812
+ ratio,
1813
+ cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
1814
+ cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
1815
+ };
1816
+ }
1774
1817
  var BaseFrameCollector = class {
1775
1818
  constructor(maxFrames = 10) {
1776
1819
  this.frames = [];
@@ -1880,7 +1923,7 @@ var EYE_QUALITY_THRESHOLDS = {
1880
1923
  minBrightness: 40,
1881
1924
  maxBrightness: 230,
1882
1925
  minContrast: 12,
1883
- glarePixelThreshold: 240,
1926
+ glareRelativeFactor: 2.5,
1884
1927
  maxGlareRatio: 0.15
1885
1928
  };
1886
1929
  function rgbaToLuminance(r, g, b) {
@@ -1907,9 +1950,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1907
1950
  }
1908
1951
  return Math.sqrt(sumSqDiff / pixelCount);
1909
1952
  }
1910
- function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1953
+ function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
1911
1954
  const pixelCount = pixels.length / 4;
1912
1955
  if (pixelCount === 0) return 0;
1956
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1957
+ const threshold = mean * relativeFactor;
1913
1958
  let glareCount = 0;
1914
1959
  for (let i = 0; i < pixels.length; i += 4) {
1915
1960
  const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
@@ -1932,7 +1977,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1932
1977
  }
1933
1978
  const brightness = analyzeEyeRegionBrightness(pixels);
1934
1979
  const contrast = analyzeEyeRegionContrast(pixels, brightness);
1935
- const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1980
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
1936
1981
  const hasGlare = glareRatio > thresholds.maxGlareRatio;
1937
1982
  if (brightness < thresholds.minBrightness) {
1938
1983
  return {
@@ -1954,16 +1999,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1954
1999
  message: "Eye region overexposed - reduce lighting"
1955
2000
  };
1956
2001
  }
1957
- if (hasGlare) {
1958
- return {
1959
- passed: false,
1960
- brightness,
1961
- contrast,
1962
- hasGlare,
1963
- glareRatio,
1964
- message: "Glare detected - adjust angle or remove glasses"
1965
- };
1966
- }
1967
2002
  if (contrast < thresholds.minContrast) {
1968
2003
  return {
1969
2004
  passed: false,
@@ -1996,6 +2031,8 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1996
2031
  BACKLIT_RATIO_THRESHOLD,
1997
2032
  BLUR_THRESHOLD_MOBILE,
1998
2033
  BaseFrameCollector,
2034
+ CAMERA_ANGLE_HIGH_RATIO,
2035
+ CAMERA_ANGLE_LOW_RATIO,
1999
2036
  DEFAULT_BLUR_THRESHOLD,
2000
2037
  DEFAULT_ENDPOINT,
2001
2038
  DEFAULT_FACE_DETECTION_TIERS,
@@ -2060,6 +2097,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2060
2097
  checkEyeRegionQuality,
2061
2098
  checkFrameQuality,
2062
2099
  decodeBase64,
2100
+ detectCameraAngle,
2063
2101
  detectFaceRoll,
2064
2102
  detectSpecularHighlights,
2065
2103
  encodeBase64,
package/dist/index.mjs CHANGED
@@ -128,7 +128,8 @@ function toLivenessResult(response) {
128
128
  score: response.score ?? 0,
129
129
  sessionId: response.session_id,
130
130
  processingMs: response.processing_ms,
131
- framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
131
+ framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
132
+ warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
132
133
  };
133
134
  }
134
135
  function toLivenessResultFromStream(response) {
@@ -145,7 +146,8 @@ function toLivenessResultFromStream(response) {
145
146
  score: response.score ?? 0,
146
147
  sessionId: response.session_id,
147
148
  processingMs: response.processing_ms ?? 0,
148
- framesProcessed: response.frames_processed ?? 0
149
+ framesProcessed: response.frames_processed ?? 0,
150
+ warnings: Array.isArray(response.warnings) ? response.warnings : void 0
149
151
  };
150
152
  }
151
153
  function generateSessionId() {
@@ -324,7 +326,8 @@ var LivenessClient = class _LivenessClient {
324
326
  session_id: options.sessionId ?? generateSessionId(),
325
327
  model: options.model ?? "10",
326
328
  source: options.source ?? "live",
327
- frames: toFrameData(frames)
329
+ frames: toFrameData(frames),
330
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
328
331
  };
329
332
  const response = await this.requestWithRetry(API_PATHS.fastCheck, {
330
333
  method: "POST",
@@ -344,7 +347,9 @@ var LivenessClient = class _LivenessClient {
344
347
  session_id: options.sessionId ?? generateSessionId(),
345
348
  model: options.model ?? "10",
346
349
  source: options.source ?? "live",
347
- crops
350
+ crops,
351
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
352
+ ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
348
353
  };
349
354
  const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
350
355
  method: "POST",
@@ -378,7 +383,8 @@ var LivenessClient = class _LivenessClient {
378
383
  return this.sendStreamFrameInternal(frameData, {
379
384
  sessionId: options.sessionId,
380
385
  model: options.model ?? "10",
381
- source: options.source ?? "live"
386
+ source: options.source ?? "live",
387
+ warnings: options.warnings
382
388
  });
383
389
  }
384
390
  /**
@@ -393,7 +399,8 @@ var LivenessClient = class _LivenessClient {
393
399
  session_id: options.sessionId,
394
400
  model: options.model,
395
401
  source: options.source,
396
- frame: frameData
402
+ frame: frameData,
403
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
397
404
  };
398
405
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
399
406
  method: "POST",
@@ -422,7 +429,8 @@ var LivenessClient = class _LivenessClient {
422
429
  const response = await this.sendStreamFrameInternal(frameData, {
423
430
  sessionId,
424
431
  model,
425
- source
432
+ source,
433
+ warnings: options.warnings
426
434
  });
427
435
  callbacks?.onFrameSent?.(index + 1, total);
428
436
  if (response.status === "buffering") {
@@ -471,7 +479,8 @@ var LivenessClient = class _LivenessClient {
471
479
  const response = await this.sendStreamFrameInternal(frameData, {
472
480
  sessionId,
473
481
  model,
474
- source
482
+ source,
483
+ warnings: options.warnings
475
484
  });
476
485
  const currentIndex = frameData.index;
477
486
  callbacks?.onFrameSent?.(currentIndex + 1, total);
@@ -1113,9 +1122,10 @@ var FEEDBACK_MESSAGES = {
1113
1122
  poor_lighting: "Improve lighting",
1114
1123
  too_dark: "Low lighting - move to a brighter area",
1115
1124
  backlit: "Backlit - try facing the light source",
1116
- // Phone orientation
1117
- phone_angle_low: "Raise your phone to eye level",
1118
- phone_tilted: "Hold your phone level",
1125
+ // Camera angle (platform-agnostic)
1126
+ camera_angle_low: "Raise camera to eye level",
1127
+ camera_angle_high: "Lower camera to eye level",
1128
+ camera_tilted: "Hold camera level",
1119
1129
  // Hand occlusion
1120
1130
  hand_detected: "Remove hand from face",
1121
1131
  // Eye region quality
@@ -1175,9 +1185,10 @@ var ES_LOCALE = {
1175
1185
  poor_lighting: "Mejora la iluminaci\xF3n",
1176
1186
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1177
1187
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1178
- // Phone orientation
1179
- phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
1180
- phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
1188
+ // Camera angle (platform-agnostic)
1189
+ camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
1190
+ camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
1191
+ camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
1181
1192
  // Hand occlusion
1182
1193
  hand_detected: "Retira la mano del rostro",
1183
1194
  // Eye region quality
@@ -1213,17 +1224,21 @@ function getCaptureQualityFeedback(state) {
1213
1224
  targetFrames,
1214
1225
  tooFarFromIdeal,
1215
1226
  tooCloseToIdeal,
1216
- phoneAngled,
1217
- phoneTilted
1227
+ cameraAngleLow,
1228
+ cameraAngleHigh,
1229
+ cameraTilted
1218
1230
  } = state;
1219
1231
  if (!hasFace) {
1220
1232
  return FEEDBACK_MESSAGES.no_face;
1221
1233
  }
1222
- if (phoneTilted) {
1223
- return FEEDBACK_MESSAGES.phone_tilted;
1234
+ if (cameraTilted) {
1235
+ return FEEDBACK_MESSAGES.camera_tilted;
1224
1236
  }
1225
- if (phoneAngled) {
1226
- return FEEDBACK_MESSAGES.phone_angle_low;
1237
+ if (cameraAngleLow) {
1238
+ return FEEDBACK_MESSAGES.camera_angle_low;
1239
+ }
1240
+ if (cameraAngleHigh) {
1241
+ return FEEDBACK_MESSAGES.camera_angle_high;
1227
1242
  }
1228
1243
  if (tooClose) {
1229
1244
  return FEEDBACK_MESSAGES.too_close;
@@ -1267,10 +1282,11 @@ function canCaptureFrame(state) {
1267
1282
  isPartialFace,
1268
1283
  tooFarFromIdeal,
1269
1284
  tooCloseToIdeal,
1270
- phoneAngled,
1271
- phoneTilted
1285
+ cameraAngleLow,
1286
+ cameraAngleHigh,
1287
+ cameraTilted
1272
1288
  } = state;
1273
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1289
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
1274
1290
  }
1275
1291
 
1276
1292
  // src/utils/validators.ts
@@ -1337,7 +1353,7 @@ function decodeBase64(base64) {
1337
1353
  }
1338
1354
 
1339
1355
  // src/utils/frameAnalysis.ts
1340
- var DEFAULT_BLUR_THRESHOLD = 100;
1356
+ var DEFAULT_BLUR_THRESHOLD = 110;
1341
1357
  var BLUR_THRESHOLD_MOBILE = 60;
1342
1358
  var BACKLIT_RATIO_THRESHOLD = 0.6;
1343
1359
  var LOW_LIGHT_THRESHOLD = 50;
@@ -1638,6 +1654,30 @@ function detectFaceRoll(landmarks) {
1638
1654
  tooTilted: roll > MAX_FACE_ROLL_DEGREES
1639
1655
  };
1640
1656
  }
1657
+ var CAMERA_ANGLE_HIGH_RATIO = 1.35;
1658
+ var CAMERA_ANGLE_LOW_RATIO = 0.75;
1659
+ function detectCameraAngle(landmarks) {
1660
+ if (landmarks.length < 153) {
1661
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1662
+ }
1663
+ const forehead = landmarks[10];
1664
+ const noseTip = landmarks[1];
1665
+ const chin = landmarks[152];
1666
+ if (!forehead || !noseTip || !chin) {
1667
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1668
+ }
1669
+ const foreheadToNose = Math.abs(forehead.y - noseTip.y);
1670
+ const noseToChin = Math.abs(noseTip.y - chin.y);
1671
+ if (noseToChin < 1e-3) {
1672
+ return { ratio: 1, cameraAbove: false, cameraBelow: false };
1673
+ }
1674
+ const ratio = foreheadToNose / noseToChin;
1675
+ return {
1676
+ ratio,
1677
+ cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
1678
+ cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
1679
+ };
1680
+ }
1641
1681
  var BaseFrameCollector = class {
1642
1682
  constructor(maxFrames = 10) {
1643
1683
  this.frames = [];
@@ -1747,7 +1787,7 @@ var EYE_QUALITY_THRESHOLDS = {
1747
1787
  minBrightness: 40,
1748
1788
  maxBrightness: 230,
1749
1789
  minContrast: 12,
1750
- glarePixelThreshold: 240,
1790
+ glareRelativeFactor: 2.5,
1751
1791
  maxGlareRatio: 0.15
1752
1792
  };
1753
1793
  function rgbaToLuminance(r, g, b) {
@@ -1774,9 +1814,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1774
1814
  }
1775
1815
  return Math.sqrt(sumSqDiff / pixelCount);
1776
1816
  }
1777
- function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1817
+ function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
1778
1818
  const pixelCount = pixels.length / 4;
1779
1819
  if (pixelCount === 0) return 0;
1820
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1821
+ const threshold = mean * relativeFactor;
1780
1822
  let glareCount = 0;
1781
1823
  for (let i = 0; i < pixels.length; i += 4) {
1782
1824
  const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
@@ -1799,7 +1841,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1799
1841
  }
1800
1842
  const brightness = analyzeEyeRegionBrightness(pixels);
1801
1843
  const contrast = analyzeEyeRegionContrast(pixels, brightness);
1802
- const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1844
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
1803
1845
  const hasGlare = glareRatio > thresholds.maxGlareRatio;
1804
1846
  if (brightness < thresholds.minBrightness) {
1805
1847
  return {
@@ -1821,16 +1863,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1821
1863
  message: "Eye region overexposed - reduce lighting"
1822
1864
  };
1823
1865
  }
1824
- if (hasGlare) {
1825
- return {
1826
- passed: false,
1827
- brightness,
1828
- contrast,
1829
- hasGlare,
1830
- glareRatio,
1831
- message: "Glare detected - adjust angle or remove glasses"
1832
- };
1833
- }
1834
1866
  if (contrast < thresholds.minContrast) {
1835
1867
  return {
1836
1868
  passed: false,
@@ -1862,6 +1894,8 @@ export {
1862
1894
  BACKLIT_RATIO_THRESHOLD,
1863
1895
  BLUR_THRESHOLD_MOBILE,
1864
1896
  BaseFrameCollector,
1897
+ CAMERA_ANGLE_HIGH_RATIO,
1898
+ CAMERA_ANGLE_LOW_RATIO,
1865
1899
  DEFAULT_BLUR_THRESHOLD,
1866
1900
  DEFAULT_ENDPOINT,
1867
1901
  DEFAULT_FACE_DETECTION_TIERS,
@@ -1926,6 +1960,7 @@ export {
1926
1960
  checkEyeRegionQuality,
1927
1961
  checkFrameQuality,
1928
1962
  decodeBase64,
1963
+ detectCameraAngle,
1929
1964
  detectFaceRoll,
1930
1965
  detectSpecularHighlights,
1931
1966
  encodeBase64,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.6.0",
3
+ "version": "2.9.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",