@moveris/shared 2.5.0 → 2.7.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
 
@@ -184,22 +184,34 @@ Model selection for liveness detection speed/accuracy trade-off.
184
184
 
185
185
  ```typescript
186
186
  type FastCheckModel =
187
+ | 'mixed-10-v2'
188
+ | 'mixed-30-v2'
189
+ | 'mixed-60-v2'
190
+ | 'mixed-90-v2'
191
+ | 'mixed-120-v2' // Active mixed-v2 models (recommended)
187
192
  | '10'
188
193
  | '50'
189
- | '250' // Standard models
190
- | 'hybrid-v2-10'
191
- | 'hybrid-v2-50' // Hybrid V2 (physiological features)
192
- | 'mixed-10'; // Mixed (visual + physiological scoring)
194
+ | '250' // Legacy standard models
195
+ | (string & object); // Forward-compatible with future model IDs
193
196
  ```
194
197
 
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 |
198
+ Active models (recommended):
199
+
200
+ | Value | Frames | Description |
201
+ | ---------------- | ------ | ------------------------------ |
202
+ | `'mixed-10-v2'` | 10 | Fast verification, low latency |
203
+ | `'mixed-30-v2'` | 30 | Balanced speed and accuracy |
204
+ | `'mixed-60-v2'` | 60 | Higher accuracy |
205
+ | `'mixed-90-v2'` | 90 | High accuracy |
206
+ | `'mixed-120-v2'` | 120 | Highest accuracy, slower |
207
+
208
+ Legacy models (still supported):
209
+
210
+ | Value | Frames | Description |
211
+ | ------- | ------ | --------------------------- |
212
+ | `'10'` | 10 | Standard — fast |
213
+ | `'50'` | 50 | Standard — balanced |
214
+ | `'250'` | 250 | Standard — highest accuracy |
203
215
 
204
216
  #### FrameSource
205
217
 
@@ -492,6 +504,8 @@ const glareRatio = detectSpecularHighlights(pixels);
492
504
  | Glare | `maxGlareRatio: 0.15` | >15% of pixels are specular highlights |
493
505
  | Occluded | `minContrast: 12` | Standard deviation of luminance too low |
494
506
 
507
+ Glare detection uses a **relative threshold** (`mean brightness × 2.5`) so sensitivity adapts to ambient lighting, avoiding false positives in bright environments.
508
+
495
509
  Custom thresholds can be passed as a second argument to `checkEyeRegionQuality()`.
496
510
 
497
511
  ### Eye Region Landmarks
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 {
@@ -215,6 +220,40 @@ interface LivenessCallbacks {
215
220
  onStateChange?: OnStateChangeCallback;
216
221
  }
217
222
 
223
+ interface FaceLandmarkPoint {
224
+ x: number;
225
+ y: number;
226
+ z: number;
227
+ }
228
+ interface LandmarkValidationResult {
229
+ valid: boolean;
230
+ message?: string;
231
+ }
232
+ declare const LANDMARK_INDEX: {
233
+ readonly NOSE_TIP: 1;
234
+ readonly UPPER_LIP: 13;
235
+ readonly LOWER_LIP: 14;
236
+ };
237
+ declare const EYE_LANDMARK_INDICES: {
238
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
239
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
240
+ };
241
+ interface EyeRegionBounds {
242
+ x: number;
243
+ y: number;
244
+ width: number;
245
+ height: number;
246
+ }
247
+ interface EyeRegionsBounds {
248
+ leftEye: EyeRegionBounds;
249
+ rightEye: EyeRegionBounds;
250
+ }
251
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
252
+ declare const LANDMARK_MIN_BOUND = 0.1;
253
+ declare const LANDMARK_MAX_BOUND = 0.9;
254
+ declare const MIN_LANDMARK_COUNT = 15;
255
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
256
+
218
257
  interface FaceBoundingBox {
219
258
  originX: number;
220
259
  originY: number;
@@ -272,15 +311,17 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
272
311
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
273
312
  declare const HIGH_ALIGNMENT = 0.85;
274
313
  declare const GOOD_ALIGNMENT = 0.5;
275
- declare const IDEAL_CROP_MULTIPLIER = 2;
276
- declare const MIN_CROP_MULTIPLIER = 1.8;
277
- declare const MAX_CROP_MULTIPLIER = 2.5;
314
+ declare const IDEAL_CROP_MULTIPLIER = 3;
315
+ declare const MIN_CROP_MULTIPLIER = 2.5;
316
+ declare const MAX_CROP_MULTIPLIER = 4;
278
317
  declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
318
+ declare const MIN_IDEAL_FACE_RATIO = 0.05;
319
+ declare const MAX_IDEAL_FACE_RATIO = 0.2;
279
320
  declare const MIN_FACE_RATIO = 0.036;
280
321
  declare const MAX_FACE_RATIO = 0.7;
281
322
  declare const FACE_CROP_OUTPUT_SIZE = 224;
282
- declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
283
- declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
323
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
324
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
284
325
  declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
285
326
  declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
286
327
  declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
@@ -306,6 +347,12 @@ declare function checkFrameQuality(options: {
306
347
  lightingAnalysis?: LightingAnalysis;
307
348
  minAlignment?: number;
308
349
  }): FrameQualityResult;
350
+ interface FaceRollResult {
351
+ roll: number;
352
+ tooTilted: boolean;
353
+ }
354
+ declare const MAX_FACE_ROLL_DEGREES = 15;
355
+ declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
309
356
  declare class BaseFrameCollector {
310
357
  protected frames: CapturedFrame[];
311
358
  protected maxFrames: number;
@@ -321,40 +368,6 @@ declare class BaseFrameCollector {
321
368
  getNextIndex(): number;
322
369
  }
323
370
 
324
- interface FaceLandmarkPoint {
325
- x: number;
326
- y: number;
327
- z: number;
328
- }
329
- interface LandmarkValidationResult {
330
- valid: boolean;
331
- message?: string;
332
- }
333
- declare const LANDMARK_INDEX: {
334
- readonly NOSE_TIP: 1;
335
- readonly UPPER_LIP: 13;
336
- readonly LOWER_LIP: 14;
337
- };
338
- declare const EYE_LANDMARK_INDICES: {
339
- readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
340
- readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
341
- };
342
- interface EyeRegionBounds {
343
- x: number;
344
- y: number;
345
- width: number;
346
- height: number;
347
- }
348
- interface EyeRegionsBounds {
349
- leftEye: EyeRegionBounds;
350
- rightEye: EyeRegionBounds;
351
- }
352
- declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
353
- declare const LANDMARK_MIN_BOUND = 0.1;
354
- declare const LANDMARK_MAX_BOUND = 0.9;
355
- declare const MIN_LANDMARK_COUNT = 15;
356
- declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
357
-
358
371
  interface DetectionResult {
359
372
  type: string;
360
373
  passed: boolean;
@@ -469,22 +482,26 @@ declare class LivenessClient {
469
482
  sessionId?: string;
470
483
  model?: FastCheckModel;
471
484
  source?: FrameSource;
485
+ warnings?: string[];
472
486
  }): Promise<LivenessResult>;
473
487
  fastCheckCrops(crops: CropData[], options?: {
474
488
  sessionId?: string;
475
489
  model?: FastCheckModel;
476
490
  source?: FrameSource;
491
+ warnings?: string[];
477
492
  }): Promise<LivenessResult>;
478
493
  streamFrame(frame: CapturedFrame, options: {
479
494
  sessionId: string;
480
495
  model?: FastCheckModel;
481
496
  source?: FrameSource;
497
+ warnings?: string[];
482
498
  }): Promise<FastCheckStreamResponse>;
483
499
  private sendStreamFrameInternal;
484
500
  fastCheckStream(frames: CapturedFrame[], options?: {
485
501
  sessionId?: string;
486
502
  model?: FastCheckModel;
487
503
  source?: FrameSource;
504
+ warnings?: string[];
488
505
  }, callbacks?: {
489
506
  onFrameSent?: (index: number, total: number) => void;
490
507
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -493,6 +510,7 @@ declare class LivenessClient {
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;
@@ -620,7 +638,7 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
620
638
  declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
621
639
  declare function isRetryableError(code: string | undefined): boolean;
622
640
 
623
- declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
641
+ declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
624
642
  declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
625
643
  declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
626
644
  declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
@@ -677,6 +695,8 @@ declare const FEEDBACK_MESSAGES: {
677
695
  readonly move_back: "Move back - face too close";
678
696
  readonly too_close: "Move back - face too close";
679
697
  readonly too_far: "Move closer - face too far";
698
+ readonly move_closer_ideal: "Move a little closer";
699
+ readonly move_back_ideal: "Move back slightly";
680
700
  readonly face_not_visible: "Center your face - edges cut off";
681
701
  readonly partial_face: "Center your face - edges cut off";
682
702
  readonly hold_still: "Hold still - image blurry";
@@ -684,6 +704,8 @@ declare const FEEDBACK_MESSAGES: {
684
704
  readonly poor_lighting: "Improve lighting";
685
705
  readonly too_dark: "Low lighting - move to a brighter area";
686
706
  readonly backlit: "Backlit - try facing the light source";
707
+ readonly phone_angle_low: "Raise your phone to eye level";
708
+ readonly phone_tilted: "Hold your phone level";
687
709
  readonly hand_detected: "Remove hand from face";
688
710
  readonly eyes_not_visible: "Eyes not clearly visible";
689
711
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -714,6 +736,10 @@ interface CaptureQualityState {
714
736
  isPartialFace: boolean;
715
737
  framesCaptured: number;
716
738
  targetFrames: number;
739
+ tooFarFromIdeal?: boolean;
740
+ tooCloseToIdeal?: boolean;
741
+ phoneAngled?: boolean;
742
+ phoneTilted?: boolean;
717
743
  }
718
744
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
719
745
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -755,13 +781,13 @@ interface EyeQualityThresholds {
755
781
  minBrightness: number;
756
782
  maxBrightness: number;
757
783
  minContrast: number;
758
- glarePixelThreshold: number;
784
+ glareRelativeFactor: number;
759
785
  maxGlareRatio: number;
760
786
  }
761
787
  declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
762
788
  declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
763
789
  declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
764
- declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
790
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
765
791
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
766
792
 
767
- 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 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 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, 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 };
793
+ 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 };
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 {
@@ -215,6 +220,40 @@ interface LivenessCallbacks {
215
220
  onStateChange?: OnStateChangeCallback;
216
221
  }
217
222
 
223
+ interface FaceLandmarkPoint {
224
+ x: number;
225
+ y: number;
226
+ z: number;
227
+ }
228
+ interface LandmarkValidationResult {
229
+ valid: boolean;
230
+ message?: string;
231
+ }
232
+ declare const LANDMARK_INDEX: {
233
+ readonly NOSE_TIP: 1;
234
+ readonly UPPER_LIP: 13;
235
+ readonly LOWER_LIP: 14;
236
+ };
237
+ declare const EYE_LANDMARK_INDICES: {
238
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
239
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
240
+ };
241
+ interface EyeRegionBounds {
242
+ x: number;
243
+ y: number;
244
+ width: number;
245
+ height: number;
246
+ }
247
+ interface EyeRegionsBounds {
248
+ leftEye: EyeRegionBounds;
249
+ rightEye: EyeRegionBounds;
250
+ }
251
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
252
+ declare const LANDMARK_MIN_BOUND = 0.1;
253
+ declare const LANDMARK_MAX_BOUND = 0.9;
254
+ declare const MIN_LANDMARK_COUNT = 15;
255
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
256
+
218
257
  interface FaceBoundingBox {
219
258
  originX: number;
220
259
  originY: number;
@@ -272,15 +311,17 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
272
311
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
273
312
  declare const HIGH_ALIGNMENT = 0.85;
274
313
  declare const GOOD_ALIGNMENT = 0.5;
275
- declare const IDEAL_CROP_MULTIPLIER = 2;
276
- declare const MIN_CROP_MULTIPLIER = 1.8;
277
- declare const MAX_CROP_MULTIPLIER = 2.5;
314
+ declare const IDEAL_CROP_MULTIPLIER = 3;
315
+ declare const MIN_CROP_MULTIPLIER = 2.5;
316
+ declare const MAX_CROP_MULTIPLIER = 4;
278
317
  declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
318
+ declare const MIN_IDEAL_FACE_RATIO = 0.05;
319
+ declare const MAX_IDEAL_FACE_RATIO = 0.2;
279
320
  declare const MIN_FACE_RATIO = 0.036;
280
321
  declare const MAX_FACE_RATIO = 0.7;
281
322
  declare const FACE_CROP_OUTPUT_SIZE = 224;
282
- declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
283
- declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
323
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
324
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
284
325
  declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
285
326
  declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
286
327
  declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
@@ -306,6 +347,12 @@ declare function checkFrameQuality(options: {
306
347
  lightingAnalysis?: LightingAnalysis;
307
348
  minAlignment?: number;
308
349
  }): FrameQualityResult;
350
+ interface FaceRollResult {
351
+ roll: number;
352
+ tooTilted: boolean;
353
+ }
354
+ declare const MAX_FACE_ROLL_DEGREES = 15;
355
+ declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
309
356
  declare class BaseFrameCollector {
310
357
  protected frames: CapturedFrame[];
311
358
  protected maxFrames: number;
@@ -321,40 +368,6 @@ declare class BaseFrameCollector {
321
368
  getNextIndex(): number;
322
369
  }
323
370
 
324
- interface FaceLandmarkPoint {
325
- x: number;
326
- y: number;
327
- z: number;
328
- }
329
- interface LandmarkValidationResult {
330
- valid: boolean;
331
- message?: string;
332
- }
333
- declare const LANDMARK_INDEX: {
334
- readonly NOSE_TIP: 1;
335
- readonly UPPER_LIP: 13;
336
- readonly LOWER_LIP: 14;
337
- };
338
- declare const EYE_LANDMARK_INDICES: {
339
- readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
340
- readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
341
- };
342
- interface EyeRegionBounds {
343
- x: number;
344
- y: number;
345
- width: number;
346
- height: number;
347
- }
348
- interface EyeRegionsBounds {
349
- leftEye: EyeRegionBounds;
350
- rightEye: EyeRegionBounds;
351
- }
352
- declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
353
- declare const LANDMARK_MIN_BOUND = 0.1;
354
- declare const LANDMARK_MAX_BOUND = 0.9;
355
- declare const MIN_LANDMARK_COUNT = 15;
356
- declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
357
-
358
371
  interface DetectionResult {
359
372
  type: string;
360
373
  passed: boolean;
@@ -469,22 +482,26 @@ declare class LivenessClient {
469
482
  sessionId?: string;
470
483
  model?: FastCheckModel;
471
484
  source?: FrameSource;
485
+ warnings?: string[];
472
486
  }): Promise<LivenessResult>;
473
487
  fastCheckCrops(crops: CropData[], options?: {
474
488
  sessionId?: string;
475
489
  model?: FastCheckModel;
476
490
  source?: FrameSource;
491
+ warnings?: string[];
477
492
  }): Promise<LivenessResult>;
478
493
  streamFrame(frame: CapturedFrame, options: {
479
494
  sessionId: string;
480
495
  model?: FastCheckModel;
481
496
  source?: FrameSource;
497
+ warnings?: string[];
482
498
  }): Promise<FastCheckStreamResponse>;
483
499
  private sendStreamFrameInternal;
484
500
  fastCheckStream(frames: CapturedFrame[], options?: {
485
501
  sessionId?: string;
486
502
  model?: FastCheckModel;
487
503
  source?: FrameSource;
504
+ warnings?: string[];
488
505
  }, callbacks?: {
489
506
  onFrameSent?: (index: number, total: number) => void;
490
507
  onFrameBuffered?: (framesReceived: number, framesRequired: number) => void;
@@ -493,6 +510,7 @@ declare class LivenessClient {
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;
@@ -620,7 +638,7 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
620
638
  declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
621
639
  declare function isRetryableError(code: string | undefined): boolean;
622
640
 
623
- declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
641
+ declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
624
642
  declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
625
643
  declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
626
644
  declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
@@ -677,6 +695,8 @@ declare const FEEDBACK_MESSAGES: {
677
695
  readonly move_back: "Move back - face too close";
678
696
  readonly too_close: "Move back - face too close";
679
697
  readonly too_far: "Move closer - face too far";
698
+ readonly move_closer_ideal: "Move a little closer";
699
+ readonly move_back_ideal: "Move back slightly";
680
700
  readonly face_not_visible: "Center your face - edges cut off";
681
701
  readonly partial_face: "Center your face - edges cut off";
682
702
  readonly hold_still: "Hold still - image blurry";
@@ -684,6 +704,8 @@ declare const FEEDBACK_MESSAGES: {
684
704
  readonly poor_lighting: "Improve lighting";
685
705
  readonly too_dark: "Low lighting - move to a brighter area";
686
706
  readonly backlit: "Backlit - try facing the light source";
707
+ readonly phone_angle_low: "Raise your phone to eye level";
708
+ readonly phone_tilted: "Hold your phone level";
687
709
  readonly hand_detected: "Remove hand from face";
688
710
  readonly eyes_not_visible: "Eyes not clearly visible";
689
711
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -714,6 +736,10 @@ interface CaptureQualityState {
714
736
  isPartialFace: boolean;
715
737
  framesCaptured: number;
716
738
  targetFrames: number;
739
+ tooFarFromIdeal?: boolean;
740
+ tooCloseToIdeal?: boolean;
741
+ phoneAngled?: boolean;
742
+ phoneTilted?: boolean;
717
743
  }
718
744
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
719
745
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -755,13 +781,13 @@ interface EyeQualityThresholds {
755
781
  minBrightness: number;
756
782
  maxBrightness: number;
757
783
  minContrast: number;
758
- glarePixelThreshold: number;
784
+ glareRelativeFactor: number;
759
785
  maxGlareRatio: number;
760
786
  }
761
787
  declare const EYE_QUALITY_THRESHOLDS: EyeQualityThresholds;
762
788
  declare function analyzeEyeRegionBrightness(pixels: Uint8Array | Uint8ClampedArray): number;
763
789
  declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray, meanBrightness?: number): number;
764
- declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
790
+ declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
765
791
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
766
792
 
767
- 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 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 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, 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 };
793
+ 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 };
package/dist/index.js CHANGED
@@ -66,12 +66,15 @@ __export(index_exports, {
66
66
  MAX_CROP_MULTIPLIER: () => MAX_CROP_MULTIPLIER,
67
67
  MAX_FACE_PERCENTAGE_IN_CROP: () => MAX_FACE_PERCENTAGE_IN_CROP,
68
68
  MAX_FACE_RATIO: () => MAX_FACE_RATIO,
69
+ MAX_FACE_ROLL_DEGREES: () => MAX_FACE_ROLL_DEGREES,
70
+ MAX_IDEAL_FACE_RATIO: () => MAX_IDEAL_FACE_RATIO,
69
71
  MIN_CAPTURE_ALIGNMENT: () => MIN_CAPTURE_ALIGNMENT,
70
72
  MIN_CROP_MULTIPLIER: () => MIN_CROP_MULTIPLIER,
71
73
  MIN_FACE_BOTTOM_MARGIN: () => MIN_FACE_BOTTOM_MARGIN,
72
74
  MIN_FACE_RATIO: () => MIN_FACE_RATIO,
73
75
  MIN_FACE_SIDE_MARGIN: () => MIN_FACE_SIDE_MARGIN,
74
76
  MIN_FACE_TOP_MARGIN: () => MIN_FACE_TOP_MARGIN,
77
+ MIN_IDEAL_FACE_RATIO: () => MIN_IDEAL_FACE_RATIO,
75
78
  MIN_LANDMARK_COUNT: () => MIN_LANDMARK_COUNT,
76
79
  MODEL_CONFIGS: () => MODEL_CONFIGS,
77
80
  OVAL_GUIDE_COLORS: () => OVAL_GUIDE_COLORS,
@@ -92,6 +95,7 @@ __export(index_exports, {
92
95
  checkEyeRegionQuality: () => checkEyeRegionQuality,
93
96
  checkFrameQuality: () => checkFrameQuality,
94
97
  decodeBase64: () => decodeBase64,
98
+ detectFaceRoll: () => detectFaceRoll,
95
99
  detectSpecularHighlights: () => detectSpecularHighlights,
96
100
  encodeBase64: () => encodeBase64,
97
101
  generateSessionId: () => generateSessionId,
@@ -257,7 +261,8 @@ function toLivenessResult(response) {
257
261
  score: response.score ?? 0,
258
262
  sessionId: response.session_id,
259
263
  processingMs: response.processing_ms,
260
- framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0
264
+ framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
265
+ warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
261
266
  };
262
267
  }
263
268
  function toLivenessResultFromStream(response) {
@@ -274,7 +279,8 @@ function toLivenessResultFromStream(response) {
274
279
  score: response.score ?? 0,
275
280
  sessionId: response.session_id,
276
281
  processingMs: response.processing_ms ?? 0,
277
- framesProcessed: response.frames_processed ?? 0
282
+ framesProcessed: response.frames_processed ?? 0,
283
+ warnings: Array.isArray(response.warnings) ? response.warnings : void 0
278
284
  };
279
285
  }
280
286
  function generateSessionId() {
@@ -453,7 +459,8 @@ var LivenessClient = class _LivenessClient {
453
459
  session_id: options.sessionId ?? generateSessionId(),
454
460
  model: options.model ?? "10",
455
461
  source: options.source ?? "live",
456
- frames: toFrameData(frames)
462
+ frames: toFrameData(frames),
463
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
457
464
  };
458
465
  const response = await this.requestWithRetry(API_PATHS.fastCheck, {
459
466
  method: "POST",
@@ -473,7 +480,8 @@ var LivenessClient = class _LivenessClient {
473
480
  session_id: options.sessionId ?? generateSessionId(),
474
481
  model: options.model ?? "10",
475
482
  source: options.source ?? "live",
476
- crops
483
+ crops,
484
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
477
485
  };
478
486
  const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
479
487
  method: "POST",
@@ -507,7 +515,8 @@ var LivenessClient = class _LivenessClient {
507
515
  return this.sendStreamFrameInternal(frameData, {
508
516
  sessionId: options.sessionId,
509
517
  model: options.model ?? "10",
510
- source: options.source ?? "live"
518
+ source: options.source ?? "live",
519
+ warnings: options.warnings
511
520
  });
512
521
  }
513
522
  /**
@@ -522,7 +531,8 @@ var LivenessClient = class _LivenessClient {
522
531
  session_id: options.sessionId,
523
532
  model: options.model,
524
533
  source: options.source,
525
- frame: frameData
534
+ frame: frameData,
535
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
526
536
  };
527
537
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
528
538
  method: "POST",
@@ -551,7 +561,8 @@ var LivenessClient = class _LivenessClient {
551
561
  const response = await this.sendStreamFrameInternal(frameData, {
552
562
  sessionId,
553
563
  model,
554
- source
564
+ source,
565
+ warnings: options.warnings
555
566
  });
556
567
  callbacks?.onFrameSent?.(index + 1, total);
557
568
  if (response.status === "buffering") {
@@ -600,7 +611,8 @@ var LivenessClient = class _LivenessClient {
600
611
  const response = await this.sendStreamFrameInternal(frameData, {
601
612
  sessionId,
602
613
  model,
603
- source
614
+ source,
615
+ warnings: options.warnings
604
616
  });
605
617
  const currentIndex = frameData.index;
606
618
  callbacks?.onFrameSent?.(currentIndex + 1, total);
@@ -1177,7 +1189,7 @@ function isRetryableError(code) {
1177
1189
  }
1178
1190
 
1179
1191
  // src/constants/feedback.ts
1180
- var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
1192
+ var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
1181
1193
  var ALIGNMENT_THRESHOLD_POOR = 0.6;
1182
1194
  var ALIGNMENT_THRESHOLD_GOOD = 0.6;
1183
1195
  var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
@@ -1230,6 +1242,8 @@ var FEEDBACK_MESSAGES = {
1230
1242
  move_back: "Move back - face too close",
1231
1243
  too_close: "Move back - face too close",
1232
1244
  too_far: "Move closer - face too far",
1245
+ move_closer_ideal: "Move a little closer",
1246
+ move_back_ideal: "Move back slightly",
1233
1247
  // Visibility issues
1234
1248
  face_not_visible: "Center your face - edges cut off",
1235
1249
  partial_face: "Center your face - edges cut off",
@@ -1240,6 +1254,9 @@ var FEEDBACK_MESSAGES = {
1240
1254
  poor_lighting: "Improve lighting",
1241
1255
  too_dark: "Low lighting - move to a brighter area",
1242
1256
  backlit: "Backlit - try facing the light source",
1257
+ // Phone orientation
1258
+ phone_angle_low: "Raise your phone to eye level",
1259
+ phone_tilted: "Hold your phone level",
1243
1260
  // Hand occlusion
1244
1261
  hand_detected: "Remove hand from face",
1245
1262
  // Eye region quality
@@ -1287,6 +1304,8 @@ var ES_LOCALE = {
1287
1304
  move_back: "Al\xE9jate - rostro muy cerca",
1288
1305
  too_close: "Al\xE9jate - rostro muy cerca",
1289
1306
  too_far: "Ac\xE9rcate - rostro muy lejos",
1307
+ move_closer_ideal: "Ac\xE9rcate un poco",
1308
+ move_back_ideal: "Al\xE9jate un poco",
1290
1309
  // Visibility issues
1291
1310
  face_not_visible: "Centra tu rostro - bordes cortados",
1292
1311
  partial_face: "Centra tu rostro - bordes cortados",
@@ -1297,6 +1316,9 @@ var ES_LOCALE = {
1297
1316
  poor_lighting: "Mejora la iluminaci\xF3n",
1298
1317
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1299
1318
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1319
+ // Phone orientation
1320
+ phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
1321
+ phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
1300
1322
  // Hand occlusion
1301
1323
  hand_detected: "Retira la mano del rostro",
1302
1324
  // Eye region quality
@@ -1329,11 +1351,21 @@ function getCaptureQualityFeedback(state) {
1329
1351
  isBlurry,
1330
1352
  isPartialFace,
1331
1353
  framesCaptured,
1332
- targetFrames
1354
+ targetFrames,
1355
+ tooFarFromIdeal,
1356
+ tooCloseToIdeal,
1357
+ phoneAngled,
1358
+ phoneTilted
1333
1359
  } = state;
1334
1360
  if (!hasFace) {
1335
1361
  return FEEDBACK_MESSAGES.no_face;
1336
1362
  }
1363
+ if (phoneTilted) {
1364
+ return FEEDBACK_MESSAGES.phone_tilted;
1365
+ }
1366
+ if (phoneAngled) {
1367
+ return FEEDBACK_MESSAGES.phone_angle_low;
1368
+ }
1337
1369
  if (tooClose) {
1338
1370
  return FEEDBACK_MESSAGES.too_close;
1339
1371
  }
@@ -1346,6 +1378,12 @@ function getCaptureQualityFeedback(state) {
1346
1378
  if (isBlurry) {
1347
1379
  return FEEDBACK_MESSAGES.blurry;
1348
1380
  }
1381
+ if (tooFarFromIdeal) {
1382
+ return FEEDBACK_MESSAGES.move_closer_ideal;
1383
+ }
1384
+ if (tooCloseToIdeal) {
1385
+ return FEEDBACK_MESSAGES.move_back_ideal;
1386
+ }
1349
1387
  if (alignment < ALIGNMENT_THRESHOLD_POOR) {
1350
1388
  return FEEDBACK_MESSAGES.move_closer_to_center;
1351
1389
  }
@@ -1361,8 +1399,19 @@ function getCaptureQualityFeedback(state) {
1361
1399
  return FEEDBACK_MESSAGES.perfect;
1362
1400
  }
1363
1401
  function canCaptureFrame(state) {
1364
- const { hasFace, alignment, tooClose, tooFar, isBlurry, isPartialFace } = state;
1365
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace;
1402
+ const {
1403
+ hasFace,
1404
+ alignment,
1405
+ tooClose,
1406
+ tooFar,
1407
+ isBlurry,
1408
+ isPartialFace,
1409
+ tooFarFromIdeal,
1410
+ tooCloseToIdeal,
1411
+ phoneAngled,
1412
+ phoneTilted
1413
+ } = state;
1414
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1366
1415
  }
1367
1416
 
1368
1417
  // src/utils/validators.ts
@@ -1439,15 +1488,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1439
1488
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1440
1489
  var HIGH_ALIGNMENT = 0.85;
1441
1490
  var GOOD_ALIGNMENT = 0.5;
1442
- var IDEAL_CROP_MULTIPLIER = 2;
1443
- var MIN_CROP_MULTIPLIER = 1.8;
1444
- var MAX_CROP_MULTIPLIER = 2.5;
1491
+ var IDEAL_CROP_MULTIPLIER = 3;
1492
+ var MIN_CROP_MULTIPLIER = 2.5;
1493
+ var MAX_CROP_MULTIPLIER = 4;
1445
1494
  var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1495
+ var MIN_IDEAL_FACE_RATIO = 0.05;
1496
+ var MAX_IDEAL_FACE_RATIO = 0.2;
1446
1497
  var MIN_FACE_RATIO = 0.036;
1447
1498
  var MAX_FACE_RATIO = 0.7;
1448
1499
  var FACE_CROP_OUTPUT_SIZE = 224;
1449
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1450
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1500
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
1501
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
1451
1502
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1452
1503
  const laplacian = [];
1453
1504
  for (let y = 1; y < height - 1; y++) {
@@ -1543,9 +1594,9 @@ var OVAL_REGION_DESKTOP = {
1543
1594
  var OVAL_REGION_MOBILE = {
1544
1595
  centerX: 0.5,
1545
1596
  centerY: 0.5,
1546
- width: 0.55,
1547
- height: 0.73
1548
- // 0.55 * (4/3)
1597
+ width: 0.48,
1598
+ height: 0.64
1599
+ // 0.48 * (4/3)
1549
1600
  };
1550
1601
  var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
1551
1602
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
@@ -1701,6 +1752,33 @@ function checkFrameQuality(options) {
1701
1752
  }
1702
1753
  return result;
1703
1754
  }
1755
+ var MAX_FACE_ROLL_DEGREES = 15;
1756
+ function detectFaceRoll(landmarks) {
1757
+ if (landmarks.length < 364) {
1758
+ return { roll: 0, tooTilted: false };
1759
+ }
1760
+ const l33 = landmarks[33];
1761
+ const l133 = landmarks[133];
1762
+ const l362 = landmarks[362];
1763
+ const l263 = landmarks[263];
1764
+ if (!l33 || !l133 || !l362 || !l263) {
1765
+ return { roll: 0, tooTilted: false };
1766
+ }
1767
+ const leftEyeX = (l33.x + l133.x) / 2;
1768
+ const leftEyeY = (l33.y + l133.y) / 2;
1769
+ const rightEyeX = (l362.x + l263.x) / 2;
1770
+ const rightEyeY = (l362.y + l263.y) / 2;
1771
+ const eyeXDist = Math.abs(leftEyeX - rightEyeX);
1772
+ const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
1773
+ if (eyeXDist < 0.01) {
1774
+ return { roll: 0, tooTilted: false };
1775
+ }
1776
+ const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
1777
+ return {
1778
+ roll,
1779
+ tooTilted: roll > MAX_FACE_ROLL_DEGREES
1780
+ };
1781
+ }
1704
1782
  var BaseFrameCollector = class {
1705
1783
  constructor(maxFrames = 10) {
1706
1784
  this.frames = [];
@@ -1810,7 +1888,7 @@ var EYE_QUALITY_THRESHOLDS = {
1810
1888
  minBrightness: 40,
1811
1889
  maxBrightness: 230,
1812
1890
  minContrast: 12,
1813
- glarePixelThreshold: 240,
1891
+ glareRelativeFactor: 2.5,
1814
1892
  maxGlareRatio: 0.15
1815
1893
  };
1816
1894
  function rgbaToLuminance(r, g, b) {
@@ -1837,9 +1915,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1837
1915
  }
1838
1916
  return Math.sqrt(sumSqDiff / pixelCount);
1839
1917
  }
1840
- function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1918
+ function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
1841
1919
  const pixelCount = pixels.length / 4;
1842
1920
  if (pixelCount === 0) return 0;
1921
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1922
+ const threshold = mean * relativeFactor;
1843
1923
  let glareCount = 0;
1844
1924
  for (let i = 0; i < pixels.length; i += 4) {
1845
1925
  const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
@@ -1862,7 +1942,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1862
1942
  }
1863
1943
  const brightness = analyzeEyeRegionBrightness(pixels);
1864
1944
  const contrast = analyzeEyeRegionContrast(pixels, brightness);
1865
- const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1945
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
1866
1946
  const hasGlare = glareRatio > thresholds.maxGlareRatio;
1867
1947
  if (brightness < thresholds.minBrightness) {
1868
1948
  return {
@@ -1884,16 +1964,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1884
1964
  message: "Eye region overexposed - reduce lighting"
1885
1965
  };
1886
1966
  }
1887
- if (hasGlare) {
1888
- return {
1889
- passed: false,
1890
- brightness,
1891
- contrast,
1892
- hasGlare,
1893
- glareRatio,
1894
- message: "Glare detected - adjust angle or remove glasses"
1895
- };
1896
- }
1897
1967
  if (contrast < thresholds.minContrast) {
1898
1968
  return {
1899
1969
  passed: false,
@@ -1961,12 +2031,15 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1961
2031
  MAX_CROP_MULTIPLIER,
1962
2032
  MAX_FACE_PERCENTAGE_IN_CROP,
1963
2033
  MAX_FACE_RATIO,
2034
+ MAX_FACE_ROLL_DEGREES,
2035
+ MAX_IDEAL_FACE_RATIO,
1964
2036
  MIN_CAPTURE_ALIGNMENT,
1965
2037
  MIN_CROP_MULTIPLIER,
1966
2038
  MIN_FACE_BOTTOM_MARGIN,
1967
2039
  MIN_FACE_RATIO,
1968
2040
  MIN_FACE_SIDE_MARGIN,
1969
2041
  MIN_FACE_TOP_MARGIN,
2042
+ MIN_IDEAL_FACE_RATIO,
1970
2043
  MIN_LANDMARK_COUNT,
1971
2044
  MODEL_CONFIGS,
1972
2045
  OVAL_GUIDE_COLORS,
@@ -1987,6 +2060,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1987
2060
  checkEyeRegionQuality,
1988
2061
  checkFrameQuality,
1989
2062
  decodeBase64,
2063
+ detectFaceRoll,
1990
2064
  detectSpecularHighlights,
1991
2065
  encodeBase64,
1992
2066
  generateSessionId,
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,8 @@ 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 } : {}
348
352
  };
349
353
  const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
350
354
  method: "POST",
@@ -378,7 +382,8 @@ var LivenessClient = class _LivenessClient {
378
382
  return this.sendStreamFrameInternal(frameData, {
379
383
  sessionId: options.sessionId,
380
384
  model: options.model ?? "10",
381
- source: options.source ?? "live"
385
+ source: options.source ?? "live",
386
+ warnings: options.warnings
382
387
  });
383
388
  }
384
389
  /**
@@ -393,7 +398,8 @@ var LivenessClient = class _LivenessClient {
393
398
  session_id: options.sessionId,
394
399
  model: options.model,
395
400
  source: options.source,
396
- frame: frameData
401
+ frame: frameData,
402
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
397
403
  };
398
404
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
399
405
  method: "POST",
@@ -422,7 +428,8 @@ var LivenessClient = class _LivenessClient {
422
428
  const response = await this.sendStreamFrameInternal(frameData, {
423
429
  sessionId,
424
430
  model,
425
- source
431
+ source,
432
+ warnings: options.warnings
426
433
  });
427
434
  callbacks?.onFrameSent?.(index + 1, total);
428
435
  if (response.status === "buffering") {
@@ -471,7 +478,8 @@ var LivenessClient = class _LivenessClient {
471
478
  const response = await this.sendStreamFrameInternal(frameData, {
472
479
  sessionId,
473
480
  model,
474
- source
481
+ source,
482
+ warnings: options.warnings
475
483
  });
476
484
  const currentIndex = frameData.index;
477
485
  callbacks?.onFrameSent?.(currentIndex + 1, total);
@@ -1048,7 +1056,7 @@ function isRetryableError(code) {
1048
1056
  }
1049
1057
 
1050
1058
  // src/constants/feedback.ts
1051
- var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
1059
+ var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
1052
1060
  var ALIGNMENT_THRESHOLD_POOR = 0.6;
1053
1061
  var ALIGNMENT_THRESHOLD_GOOD = 0.6;
1054
1062
  var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
@@ -1101,6 +1109,8 @@ var FEEDBACK_MESSAGES = {
1101
1109
  move_back: "Move back - face too close",
1102
1110
  too_close: "Move back - face too close",
1103
1111
  too_far: "Move closer - face too far",
1112
+ move_closer_ideal: "Move a little closer",
1113
+ move_back_ideal: "Move back slightly",
1104
1114
  // Visibility issues
1105
1115
  face_not_visible: "Center your face - edges cut off",
1106
1116
  partial_face: "Center your face - edges cut off",
@@ -1111,6 +1121,9 @@ var FEEDBACK_MESSAGES = {
1111
1121
  poor_lighting: "Improve lighting",
1112
1122
  too_dark: "Low lighting - move to a brighter area",
1113
1123
  backlit: "Backlit - try facing the light source",
1124
+ // Phone orientation
1125
+ phone_angle_low: "Raise your phone to eye level",
1126
+ phone_tilted: "Hold your phone level",
1114
1127
  // Hand occlusion
1115
1128
  hand_detected: "Remove hand from face",
1116
1129
  // Eye region quality
@@ -1158,6 +1171,8 @@ var ES_LOCALE = {
1158
1171
  move_back: "Al\xE9jate - rostro muy cerca",
1159
1172
  too_close: "Al\xE9jate - rostro muy cerca",
1160
1173
  too_far: "Ac\xE9rcate - rostro muy lejos",
1174
+ move_closer_ideal: "Ac\xE9rcate un poco",
1175
+ move_back_ideal: "Al\xE9jate un poco",
1161
1176
  // Visibility issues
1162
1177
  face_not_visible: "Centra tu rostro - bordes cortados",
1163
1178
  partial_face: "Centra tu rostro - bordes cortados",
@@ -1168,6 +1183,9 @@ var ES_LOCALE = {
1168
1183
  poor_lighting: "Mejora la iluminaci\xF3n",
1169
1184
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1170
1185
  backlit: "Contraluz - intenta mirar hacia la fuente de luz",
1186
+ // Phone orientation
1187
+ phone_angle_low: "Levanta el tel\xE9fono a la altura de los ojos",
1188
+ phone_tilted: "Mant\xE9n el tel\xE9fono nivelado",
1171
1189
  // Hand occlusion
1172
1190
  hand_detected: "Retira la mano del rostro",
1173
1191
  // Eye region quality
@@ -1200,11 +1218,21 @@ function getCaptureQualityFeedback(state) {
1200
1218
  isBlurry,
1201
1219
  isPartialFace,
1202
1220
  framesCaptured,
1203
- targetFrames
1221
+ targetFrames,
1222
+ tooFarFromIdeal,
1223
+ tooCloseToIdeal,
1224
+ phoneAngled,
1225
+ phoneTilted
1204
1226
  } = state;
1205
1227
  if (!hasFace) {
1206
1228
  return FEEDBACK_MESSAGES.no_face;
1207
1229
  }
1230
+ if (phoneTilted) {
1231
+ return FEEDBACK_MESSAGES.phone_tilted;
1232
+ }
1233
+ if (phoneAngled) {
1234
+ return FEEDBACK_MESSAGES.phone_angle_low;
1235
+ }
1208
1236
  if (tooClose) {
1209
1237
  return FEEDBACK_MESSAGES.too_close;
1210
1238
  }
@@ -1217,6 +1245,12 @@ function getCaptureQualityFeedback(state) {
1217
1245
  if (isBlurry) {
1218
1246
  return FEEDBACK_MESSAGES.blurry;
1219
1247
  }
1248
+ if (tooFarFromIdeal) {
1249
+ return FEEDBACK_MESSAGES.move_closer_ideal;
1250
+ }
1251
+ if (tooCloseToIdeal) {
1252
+ return FEEDBACK_MESSAGES.move_back_ideal;
1253
+ }
1220
1254
  if (alignment < ALIGNMENT_THRESHOLD_POOR) {
1221
1255
  return FEEDBACK_MESSAGES.move_closer_to_center;
1222
1256
  }
@@ -1232,8 +1266,19 @@ function getCaptureQualityFeedback(state) {
1232
1266
  return FEEDBACK_MESSAGES.perfect;
1233
1267
  }
1234
1268
  function canCaptureFrame(state) {
1235
- const { hasFace, alignment, tooClose, tooFar, isBlurry, isPartialFace } = state;
1236
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace;
1269
+ const {
1270
+ hasFace,
1271
+ alignment,
1272
+ tooClose,
1273
+ tooFar,
1274
+ isBlurry,
1275
+ isPartialFace,
1276
+ tooFarFromIdeal,
1277
+ tooCloseToIdeal,
1278
+ phoneAngled,
1279
+ phoneTilted
1280
+ } = state;
1281
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1237
1282
  }
1238
1283
 
1239
1284
  // src/utils/validators.ts
@@ -1310,15 +1355,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1310
1355
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1311
1356
  var HIGH_ALIGNMENT = 0.85;
1312
1357
  var GOOD_ALIGNMENT = 0.5;
1313
- var IDEAL_CROP_MULTIPLIER = 2;
1314
- var MIN_CROP_MULTIPLIER = 1.8;
1315
- var MAX_CROP_MULTIPLIER = 2.5;
1358
+ var IDEAL_CROP_MULTIPLIER = 3;
1359
+ var MIN_CROP_MULTIPLIER = 2.5;
1360
+ var MAX_CROP_MULTIPLIER = 4;
1316
1361
  var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1362
+ var MIN_IDEAL_FACE_RATIO = 0.05;
1363
+ var MAX_IDEAL_FACE_RATIO = 0.2;
1317
1364
  var MIN_FACE_RATIO = 0.036;
1318
1365
  var MAX_FACE_RATIO = 0.7;
1319
1366
  var FACE_CROP_OUTPUT_SIZE = 224;
1320
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1321
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1367
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
1368
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
1322
1369
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1323
1370
  const laplacian = [];
1324
1371
  for (let y = 1; y < height - 1; y++) {
@@ -1414,9 +1461,9 @@ var OVAL_REGION_DESKTOP = {
1414
1461
  var OVAL_REGION_MOBILE = {
1415
1462
  centerX: 0.5,
1416
1463
  centerY: 0.5,
1417
- width: 0.55,
1418
- height: 0.73
1419
- // 0.55 * (4/3)
1464
+ width: 0.48,
1465
+ height: 0.64
1466
+ // 0.48 * (4/3)
1420
1467
  };
1421
1468
  var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
1422
1469
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
@@ -1572,6 +1619,33 @@ function checkFrameQuality(options) {
1572
1619
  }
1573
1620
  return result;
1574
1621
  }
1622
+ var MAX_FACE_ROLL_DEGREES = 15;
1623
+ function detectFaceRoll(landmarks) {
1624
+ if (landmarks.length < 364) {
1625
+ return { roll: 0, tooTilted: false };
1626
+ }
1627
+ const l33 = landmarks[33];
1628
+ const l133 = landmarks[133];
1629
+ const l362 = landmarks[362];
1630
+ const l263 = landmarks[263];
1631
+ if (!l33 || !l133 || !l362 || !l263) {
1632
+ return { roll: 0, tooTilted: false };
1633
+ }
1634
+ const leftEyeX = (l33.x + l133.x) / 2;
1635
+ const leftEyeY = (l33.y + l133.y) / 2;
1636
+ const rightEyeX = (l362.x + l263.x) / 2;
1637
+ const rightEyeY = (l362.y + l263.y) / 2;
1638
+ const eyeXDist = Math.abs(leftEyeX - rightEyeX);
1639
+ const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
1640
+ if (eyeXDist < 0.01) {
1641
+ return { roll: 0, tooTilted: false };
1642
+ }
1643
+ const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
1644
+ return {
1645
+ roll,
1646
+ tooTilted: roll > MAX_FACE_ROLL_DEGREES
1647
+ };
1648
+ }
1575
1649
  var BaseFrameCollector = class {
1576
1650
  constructor(maxFrames = 10) {
1577
1651
  this.frames = [];
@@ -1681,7 +1755,7 @@ var EYE_QUALITY_THRESHOLDS = {
1681
1755
  minBrightness: 40,
1682
1756
  maxBrightness: 230,
1683
1757
  minContrast: 12,
1684
- glarePixelThreshold: 240,
1758
+ glareRelativeFactor: 2.5,
1685
1759
  maxGlareRatio: 0.15
1686
1760
  };
1687
1761
  function rgbaToLuminance(r, g, b) {
@@ -1708,9 +1782,11 @@ function analyzeEyeRegionContrast(pixels, meanBrightness) {
1708
1782
  }
1709
1783
  return Math.sqrt(sumSqDiff / pixelCount);
1710
1784
  }
1711
- function detectSpecularHighlights(pixels, threshold = EYE_QUALITY_THRESHOLDS.glarePixelThreshold) {
1785
+ function detectSpecularHighlights(pixels, relativeFactor = EYE_QUALITY_THRESHOLDS.glareRelativeFactor, meanBrightness) {
1712
1786
  const pixelCount = pixels.length / 4;
1713
1787
  if (pixelCount === 0) return 0;
1788
+ const mean = meanBrightness ?? analyzeEyeRegionBrightness(pixels);
1789
+ const threshold = mean * relativeFactor;
1714
1790
  let glareCount = 0;
1715
1791
  for (let i = 0; i < pixels.length; i += 4) {
1716
1792
  const lum = rgbaToLuminance(pixels[i], pixels[i + 1], pixels[i + 2]);
@@ -1733,7 +1809,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1733
1809
  }
1734
1810
  const brightness = analyzeEyeRegionBrightness(pixels);
1735
1811
  const contrast = analyzeEyeRegionContrast(pixels, brightness);
1736
- const glareRatio = detectSpecularHighlights(pixels, thresholds.glarePixelThreshold);
1812
+ const glareRatio = detectSpecularHighlights(pixels, thresholds.glareRelativeFactor, brightness);
1737
1813
  const hasGlare = glareRatio > thresholds.maxGlareRatio;
1738
1814
  if (brightness < thresholds.minBrightness) {
1739
1815
  return {
@@ -1755,16 +1831,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1755
1831
  message: "Eye region overexposed - reduce lighting"
1756
1832
  };
1757
1833
  }
1758
- if (hasGlare) {
1759
- return {
1760
- passed: false,
1761
- brightness,
1762
- contrast,
1763
- hasGlare,
1764
- glareRatio,
1765
- message: "Glare detected - adjust angle or remove glasses"
1766
- };
1767
- }
1768
1834
  if (contrast < thresholds.minContrast) {
1769
1835
  return {
1770
1836
  passed: false,
@@ -1831,12 +1897,15 @@ export {
1831
1897
  MAX_CROP_MULTIPLIER,
1832
1898
  MAX_FACE_PERCENTAGE_IN_CROP,
1833
1899
  MAX_FACE_RATIO,
1900
+ MAX_FACE_ROLL_DEGREES,
1901
+ MAX_IDEAL_FACE_RATIO,
1834
1902
  MIN_CAPTURE_ALIGNMENT,
1835
1903
  MIN_CROP_MULTIPLIER,
1836
1904
  MIN_FACE_BOTTOM_MARGIN,
1837
1905
  MIN_FACE_RATIO,
1838
1906
  MIN_FACE_SIDE_MARGIN,
1839
1907
  MIN_FACE_TOP_MARGIN,
1908
+ MIN_IDEAL_FACE_RATIO,
1840
1909
  MIN_LANDMARK_COUNT,
1841
1910
  MODEL_CONFIGS,
1842
1911
  OVAL_GUIDE_COLORS,
@@ -1857,6 +1926,7 @@ export {
1857
1926
  checkEyeRegionQuality,
1858
1927
  checkFrameQuality,
1859
1928
  decodeBase64,
1929
+ detectFaceRoll,
1860
1930
  detectSpecularHighlights,
1861
1931
  encodeBase64,
1862
1932
  generateSessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",