@moveris/shared 2.4.0 → 2.6.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/dist/index.d.mts CHANGED
@@ -215,6 +215,40 @@ interface LivenessCallbacks {
215
215
  onStateChange?: OnStateChangeCallback;
216
216
  }
217
217
 
218
+ interface FaceLandmarkPoint {
219
+ x: number;
220
+ y: number;
221
+ z: number;
222
+ }
223
+ interface LandmarkValidationResult {
224
+ valid: boolean;
225
+ message?: string;
226
+ }
227
+ declare const LANDMARK_INDEX: {
228
+ readonly NOSE_TIP: 1;
229
+ readonly UPPER_LIP: 13;
230
+ readonly LOWER_LIP: 14;
231
+ };
232
+ declare const EYE_LANDMARK_INDICES: {
233
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
234
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
235
+ };
236
+ interface EyeRegionBounds {
237
+ x: number;
238
+ y: number;
239
+ width: number;
240
+ height: number;
241
+ }
242
+ interface EyeRegionsBounds {
243
+ leftEye: EyeRegionBounds;
244
+ rightEye: EyeRegionBounds;
245
+ }
246
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
247
+ declare const LANDMARK_MIN_BOUND = 0.1;
248
+ declare const LANDMARK_MAX_BOUND = 0.9;
249
+ declare const MIN_LANDMARK_COUNT = 15;
250
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
251
+
218
252
  interface FaceBoundingBox {
219
253
  originX: number;
220
254
  originY: number;
@@ -263,7 +297,7 @@ interface FrameQualityResult {
263
297
  rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
264
298
  }
265
299
  declare const DEFAULT_BLUR_THRESHOLD = 100;
266
- declare const BLUR_THRESHOLD_MOBILE = 150;
300
+ declare const BLUR_THRESHOLD_MOBILE = 60;
267
301
  declare const BACKLIT_RATIO_THRESHOLD = 0.6;
268
302
  declare const LOW_LIGHT_THRESHOLD = 50;
269
303
  declare const MIN_FACE_TOP_MARGIN = 0.1;
@@ -272,20 +306,24 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
272
306
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
273
307
  declare const HIGH_ALIGNMENT = 0.85;
274
308
  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;
309
+ declare const IDEAL_CROP_MULTIPLIER = 3;
310
+ declare const MIN_CROP_MULTIPLIER = 2.5;
311
+ declare const MAX_CROP_MULTIPLIER = 4;
278
312
  declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
313
+ declare const MIN_IDEAL_FACE_RATIO = 0.05;
314
+ declare const MAX_IDEAL_FACE_RATIO = 0.2;
279
315
  declare const MIN_FACE_RATIO = 0.036;
280
316
  declare const MAX_FACE_RATIO = 0.7;
281
317
  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;
318
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
319
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
284
320
  declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
285
321
  declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
286
322
  declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
287
323
  declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
288
324
  declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
325
+ declare const OVAL_REGION_DESKTOP: OvalRegion;
326
+ declare const OVAL_REGION_MOBILE: OvalRegion;
289
327
  declare const DEFAULT_OVAL_REGION: OvalRegion;
290
328
  declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
291
329
  declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
@@ -304,6 +342,12 @@ declare function checkFrameQuality(options: {
304
342
  lightingAnalysis?: LightingAnalysis;
305
343
  minAlignment?: number;
306
344
  }): FrameQualityResult;
345
+ interface FaceRollResult {
346
+ roll: number;
347
+ tooTilted: boolean;
348
+ }
349
+ declare const MAX_FACE_ROLL_DEGREES = 15;
350
+ declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
307
351
  declare class BaseFrameCollector {
308
352
  protected frames: CapturedFrame[];
309
353
  protected maxFrames: number;
@@ -319,40 +363,6 @@ declare class BaseFrameCollector {
319
363
  getNextIndex(): number;
320
364
  }
321
365
 
322
- interface FaceLandmarkPoint {
323
- x: number;
324
- y: number;
325
- z: number;
326
- }
327
- interface LandmarkValidationResult {
328
- valid: boolean;
329
- message?: string;
330
- }
331
- declare const LANDMARK_INDEX: {
332
- readonly NOSE_TIP: 1;
333
- readonly UPPER_LIP: 13;
334
- readonly LOWER_LIP: 14;
335
- };
336
- declare const EYE_LANDMARK_INDICES: {
337
- readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
338
- readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
339
- };
340
- interface EyeRegionBounds {
341
- x: number;
342
- y: number;
343
- width: number;
344
- height: number;
345
- }
346
- interface EyeRegionsBounds {
347
- leftEye: EyeRegionBounds;
348
- rightEye: EyeRegionBounds;
349
- }
350
- declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
351
- declare const LANDMARK_MIN_BOUND = 0.1;
352
- declare const LANDMARK_MAX_BOUND = 0.9;
353
- declare const MIN_LANDMARK_COUNT = 15;
354
- declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
355
-
356
366
  interface DetectionResult {
357
367
  type: string;
358
368
  passed: boolean;
@@ -618,9 +628,9 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
618
628
  declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
619
629
  declare function isRetryableError(code: string | undefined): boolean;
620
630
 
621
- declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
622
- declare const ALIGNMENT_THRESHOLD_POOR = 0.5;
623
- declare const ALIGNMENT_THRESHOLD_GOOD = 0.5;
631
+ declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
632
+ declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
633
+ declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
624
634
  declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
625
635
  type OvalGuideState = 'no_face' | 'poor' | 'good' | 'perfect';
626
636
  declare const OVAL_GUIDE_COLORS: {
@@ -675,6 +685,8 @@ declare const FEEDBACK_MESSAGES: {
675
685
  readonly move_back: "Move back - face too close";
676
686
  readonly too_close: "Move back - face too close";
677
687
  readonly too_far: "Move closer - face too far";
688
+ readonly move_closer_ideal: "Move a little closer";
689
+ readonly move_back_ideal: "Move back slightly";
678
690
  readonly face_not_visible: "Center your face - edges cut off";
679
691
  readonly partial_face: "Center your face - edges cut off";
680
692
  readonly hold_still: "Hold still - image blurry";
@@ -682,6 +694,8 @@ declare const FEEDBACK_MESSAGES: {
682
694
  readonly poor_lighting: "Improve lighting";
683
695
  readonly too_dark: "Low lighting - move to a brighter area";
684
696
  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";
685
699
  readonly hand_detected: "Remove hand from face";
686
700
  readonly eyes_not_visible: "Eyes not clearly visible";
687
701
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -712,6 +726,10 @@ interface CaptureQualityState {
712
726
  isPartialFace: boolean;
713
727
  framesCaptured: number;
714
728
  targetFrames: number;
729
+ tooFarFromIdeal?: boolean;
730
+ tooCloseToIdeal?: boolean;
731
+ phoneAngled?: boolean;
732
+ phoneTilted?: boolean;
715
733
  }
716
734
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
717
735
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -762,4 +780,4 @@ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray
762
780
  declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
763
781
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
764
782
 
765
- 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, 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 };
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 };
package/dist/index.d.ts CHANGED
@@ -215,6 +215,40 @@ interface LivenessCallbacks {
215
215
  onStateChange?: OnStateChangeCallback;
216
216
  }
217
217
 
218
+ interface FaceLandmarkPoint {
219
+ x: number;
220
+ y: number;
221
+ z: number;
222
+ }
223
+ interface LandmarkValidationResult {
224
+ valid: boolean;
225
+ message?: string;
226
+ }
227
+ declare const LANDMARK_INDEX: {
228
+ readonly NOSE_TIP: 1;
229
+ readonly UPPER_LIP: 13;
230
+ readonly LOWER_LIP: 14;
231
+ };
232
+ declare const EYE_LANDMARK_INDICES: {
233
+ readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
234
+ readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
235
+ };
236
+ interface EyeRegionBounds {
237
+ x: number;
238
+ y: number;
239
+ width: number;
240
+ height: number;
241
+ }
242
+ interface EyeRegionsBounds {
243
+ leftEye: EyeRegionBounds;
244
+ rightEye: EyeRegionBounds;
245
+ }
246
+ declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
247
+ declare const LANDMARK_MIN_BOUND = 0.1;
248
+ declare const LANDMARK_MAX_BOUND = 0.9;
249
+ declare const MIN_LANDMARK_COUNT = 15;
250
+ declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
251
+
218
252
  interface FaceBoundingBox {
219
253
  originX: number;
220
254
  originY: number;
@@ -263,7 +297,7 @@ interface FrameQualityResult {
263
297
  rejectionReason?: 'blur' | 'backlit' | 'low_light' | 'partial_face' | 'no_face' | 'too_close' | 'too_far' | 'misaligned';
264
298
  }
265
299
  declare const DEFAULT_BLUR_THRESHOLD = 100;
266
- declare const BLUR_THRESHOLD_MOBILE = 150;
300
+ declare const BLUR_THRESHOLD_MOBILE = 60;
267
301
  declare const BACKLIT_RATIO_THRESHOLD = 0.6;
268
302
  declare const LOW_LIGHT_THRESHOLD = 50;
269
303
  declare const MIN_FACE_TOP_MARGIN = 0.1;
@@ -272,20 +306,24 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
272
306
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
273
307
  declare const HIGH_ALIGNMENT = 0.85;
274
308
  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;
309
+ declare const IDEAL_CROP_MULTIPLIER = 3;
310
+ declare const MIN_CROP_MULTIPLIER = 2.5;
311
+ declare const MAX_CROP_MULTIPLIER = 4;
278
312
  declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
313
+ declare const MIN_IDEAL_FACE_RATIO = 0.05;
314
+ declare const MAX_IDEAL_FACE_RATIO = 0.2;
279
315
  declare const MIN_FACE_RATIO = 0.036;
280
316
  declare const MAX_FACE_RATIO = 0.7;
281
317
  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;
318
+ declare const MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
319
+ declare const TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
284
320
  declare function analyzeBlur(grayscalePixels: number[], width: number, height: number, threshold?: number): BlurAnalysis;
285
321
  declare function rgbaToGrayscale(rgbaPixels: Uint8ClampedArray | number[]): number[];
286
322
  declare function calculateBrightness(rgbaPixels: Uint8ClampedArray | number[]): number;
287
323
  declare function analyzeLighting(faceBrightness: number, backgroundBrightness: number): LightingAnalysis;
288
324
  declare function isFaceFullyVisible(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceVisibilityResult;
325
+ declare const OVAL_REGION_DESKTOP: OvalRegion;
326
+ declare const OVAL_REGION_MOBILE: OvalRegion;
289
327
  declare const DEFAULT_OVAL_REGION: OvalRegion;
290
328
  declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
291
329
  declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
@@ -304,6 +342,12 @@ declare function checkFrameQuality(options: {
304
342
  lightingAnalysis?: LightingAnalysis;
305
343
  minAlignment?: number;
306
344
  }): FrameQualityResult;
345
+ interface FaceRollResult {
346
+ roll: number;
347
+ tooTilted: boolean;
348
+ }
349
+ declare const MAX_FACE_ROLL_DEGREES = 15;
350
+ declare function detectFaceRoll(landmarks: FaceLandmarkPoint[]): FaceRollResult;
307
351
  declare class BaseFrameCollector {
308
352
  protected frames: CapturedFrame[];
309
353
  protected maxFrames: number;
@@ -319,40 +363,6 @@ declare class BaseFrameCollector {
319
363
  getNextIndex(): number;
320
364
  }
321
365
 
322
- interface FaceLandmarkPoint {
323
- x: number;
324
- y: number;
325
- z: number;
326
- }
327
- interface LandmarkValidationResult {
328
- valid: boolean;
329
- message?: string;
330
- }
331
- declare const LANDMARK_INDEX: {
332
- readonly NOSE_TIP: 1;
333
- readonly UPPER_LIP: 13;
334
- readonly LOWER_LIP: 14;
335
- };
336
- declare const EYE_LANDMARK_INDICES: {
337
- readonly RIGHT_EYE: readonly [33, 7, 163, 144, 145, 153, 154, 155, 133, 173, 157, 158, 159, 160, 161, 246];
338
- readonly LEFT_EYE: readonly [362, 382, 381, 380, 374, 373, 390, 249, 263, 466, 388, 387, 386, 385, 384, 398];
339
- };
340
- interface EyeRegionBounds {
341
- x: number;
342
- y: number;
343
- width: number;
344
- height: number;
345
- }
346
- interface EyeRegionsBounds {
347
- leftEye: EyeRegionBounds;
348
- rightEye: EyeRegionBounds;
349
- }
350
- declare function getEyeRegionBounds(landmarks: FaceLandmarkPoint[]): EyeRegionsBounds | null;
351
- declare const LANDMARK_MIN_BOUND = 0.1;
352
- declare const LANDMARK_MAX_BOUND = 0.9;
353
- declare const MIN_LANDMARK_COUNT = 15;
354
- declare function validateFaceLandmarks(landmarks: FaceLandmarkPoint[] | undefined): LandmarkValidationResult;
355
-
356
366
  interface DetectionResult {
357
367
  type: string;
358
368
  passed: boolean;
@@ -618,9 +628,9 @@ declare const ERROR_MESSAGES_ES: Record<string, string>;
618
628
  declare function getApiErrorMessage(code: string | undefined, message?: string, customMessages?: Record<string, string>): string;
619
629
  declare function isRetryableError(code: string | undefined): boolean;
620
630
 
621
- declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
622
- declare const ALIGNMENT_THRESHOLD_POOR = 0.5;
623
- declare const ALIGNMENT_THRESHOLD_GOOD = 0.5;
631
+ declare const ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
632
+ declare const ALIGNMENT_THRESHOLD_POOR = 0.6;
633
+ declare const ALIGNMENT_THRESHOLD_GOOD = 0.6;
624
634
  declare const ALIGNMENT_THRESHOLD_PERFECT = 0.85;
625
635
  type OvalGuideState = 'no_face' | 'poor' | 'good' | 'perfect';
626
636
  declare const OVAL_GUIDE_COLORS: {
@@ -675,6 +685,8 @@ declare const FEEDBACK_MESSAGES: {
675
685
  readonly move_back: "Move back - face too close";
676
686
  readonly too_close: "Move back - face too close";
677
687
  readonly too_far: "Move closer - face too far";
688
+ readonly move_closer_ideal: "Move a little closer";
689
+ readonly move_back_ideal: "Move back slightly";
678
690
  readonly face_not_visible: "Center your face - edges cut off";
679
691
  readonly partial_face: "Center your face - edges cut off";
680
692
  readonly hold_still: "Hold still - image blurry";
@@ -682,6 +694,8 @@ declare const FEEDBACK_MESSAGES: {
682
694
  readonly poor_lighting: "Improve lighting";
683
695
  readonly too_dark: "Low lighting - move to a brighter area";
684
696
  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";
685
699
  readonly hand_detected: "Remove hand from face";
686
700
  readonly eyes_not_visible: "Eyes not clearly visible";
687
701
  readonly eyes_shadowed: "Eyes are in shadow - improve lighting";
@@ -712,6 +726,10 @@ interface CaptureQualityState {
712
726
  isPartialFace: boolean;
713
727
  framesCaptured: number;
714
728
  targetFrames: number;
729
+ tooFarFromIdeal?: boolean;
730
+ tooCloseToIdeal?: boolean;
731
+ phoneAngled?: boolean;
732
+ phoneTilted?: boolean;
715
733
  }
716
734
  declare function getCaptureQualityFeedback(state: CaptureQualityState): string;
717
735
  declare function canCaptureFrame(state: Omit<CaptureQualityState, 'framesCaptured' | 'targetFrames' | 'isCapturing'>): boolean;
@@ -762,4 +780,4 @@ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray
762
780
  declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, threshold?: number): number;
763
781
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
764
782
 
765
- 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, 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 };
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 };
package/dist/index.js CHANGED
@@ -66,16 +66,21 @@ __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,
78
81
  OVAL_GUIDE_STYLES: () => OVAL_GUIDE_STYLES,
82
+ OVAL_REGION_DESKTOP: () => OVAL_REGION_DESKTOP,
83
+ OVAL_REGION_MOBILE: () => OVAL_REGION_MOBILE,
79
84
  RETRY_CONFIG: () => RETRY_CONFIG,
80
85
  TARGET_FACE_PERCENTAGE_IN_CROP: () => TARGET_FACE_PERCENTAGE_IN_CROP,
81
86
  analyzeBlur: () => analyzeBlur,
@@ -90,6 +95,7 @@ __export(index_exports, {
90
95
  checkEyeRegionQuality: () => checkEyeRegionQuality,
91
96
  checkFrameQuality: () => checkFrameQuality,
92
97
  decodeBase64: () => decodeBase64,
98
+ detectFaceRoll: () => detectFaceRoll,
93
99
  detectSpecularHighlights: () => detectSpecularHighlights,
94
100
  encodeBase64: () => encodeBase64,
95
101
  generateSessionId: () => generateSessionId,
@@ -1175,9 +1181,9 @@ function isRetryableError(code) {
1175
1181
  }
1176
1182
 
1177
1183
  // src/constants/feedback.ts
1178
- var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
1179
- var ALIGNMENT_THRESHOLD_POOR = 0.5;
1180
- var ALIGNMENT_THRESHOLD_GOOD = 0.5;
1184
+ var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
1185
+ var ALIGNMENT_THRESHOLD_POOR = 0.6;
1186
+ var ALIGNMENT_THRESHOLD_GOOD = 0.6;
1181
1187
  var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
1182
1188
  var OVAL_GUIDE_COLORS = {
1183
1189
  no_face: "#ef4444",
@@ -1228,6 +1234,8 @@ var FEEDBACK_MESSAGES = {
1228
1234
  move_back: "Move back - face too close",
1229
1235
  too_close: "Move back - face too close",
1230
1236
  too_far: "Move closer - face too far",
1237
+ move_closer_ideal: "Move a little closer",
1238
+ move_back_ideal: "Move back slightly",
1231
1239
  // Visibility issues
1232
1240
  face_not_visible: "Center your face - edges cut off",
1233
1241
  partial_face: "Center your face - edges cut off",
@@ -1238,6 +1246,9 @@ var FEEDBACK_MESSAGES = {
1238
1246
  poor_lighting: "Improve lighting",
1239
1247
  too_dark: "Low lighting - move to a brighter area",
1240
1248
  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",
1241
1252
  // Hand occlusion
1242
1253
  hand_detected: "Remove hand from face",
1243
1254
  // Eye region quality
@@ -1285,6 +1296,8 @@ var ES_LOCALE = {
1285
1296
  move_back: "Al\xE9jate - rostro muy cerca",
1286
1297
  too_close: "Al\xE9jate - rostro muy cerca",
1287
1298
  too_far: "Ac\xE9rcate - rostro muy lejos",
1299
+ move_closer_ideal: "Ac\xE9rcate un poco",
1300
+ move_back_ideal: "Al\xE9jate un poco",
1288
1301
  // Visibility issues
1289
1302
  face_not_visible: "Centra tu rostro - bordes cortados",
1290
1303
  partial_face: "Centra tu rostro - bordes cortados",
@@ -1295,6 +1308,9 @@ var ES_LOCALE = {
1295
1308
  poor_lighting: "Mejora la iluminaci\xF3n",
1296
1309
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1297
1310
  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",
1298
1314
  // Hand occlusion
1299
1315
  hand_detected: "Retira la mano del rostro",
1300
1316
  // Eye region quality
@@ -1327,11 +1343,21 @@ function getCaptureQualityFeedback(state) {
1327
1343
  isBlurry,
1328
1344
  isPartialFace,
1329
1345
  framesCaptured,
1330
- targetFrames
1346
+ targetFrames,
1347
+ tooFarFromIdeal,
1348
+ tooCloseToIdeal,
1349
+ phoneAngled,
1350
+ phoneTilted
1331
1351
  } = state;
1332
1352
  if (!hasFace) {
1333
1353
  return FEEDBACK_MESSAGES.no_face;
1334
1354
  }
1355
+ if (phoneTilted) {
1356
+ return FEEDBACK_MESSAGES.phone_tilted;
1357
+ }
1358
+ if (phoneAngled) {
1359
+ return FEEDBACK_MESSAGES.phone_angle_low;
1360
+ }
1335
1361
  if (tooClose) {
1336
1362
  return FEEDBACK_MESSAGES.too_close;
1337
1363
  }
@@ -1344,6 +1370,12 @@ function getCaptureQualityFeedback(state) {
1344
1370
  if (isBlurry) {
1345
1371
  return FEEDBACK_MESSAGES.blurry;
1346
1372
  }
1373
+ if (tooFarFromIdeal) {
1374
+ return FEEDBACK_MESSAGES.move_closer_ideal;
1375
+ }
1376
+ if (tooCloseToIdeal) {
1377
+ return FEEDBACK_MESSAGES.move_back_ideal;
1378
+ }
1347
1379
  if (alignment < ALIGNMENT_THRESHOLD_POOR) {
1348
1380
  return FEEDBACK_MESSAGES.move_closer_to_center;
1349
1381
  }
@@ -1359,8 +1391,19 @@ function getCaptureQualityFeedback(state) {
1359
1391
  return FEEDBACK_MESSAGES.perfect;
1360
1392
  }
1361
1393
  function canCaptureFrame(state) {
1362
- const { hasFace, alignment, tooClose, tooFar, isBlurry, isPartialFace } = state;
1363
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace;
1394
+ const {
1395
+ hasFace,
1396
+ alignment,
1397
+ tooClose,
1398
+ tooFar,
1399
+ isBlurry,
1400
+ isPartialFace,
1401
+ tooFarFromIdeal,
1402
+ tooCloseToIdeal,
1403
+ phoneAngled,
1404
+ phoneTilted
1405
+ } = state;
1406
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1364
1407
  }
1365
1408
 
1366
1409
  // src/utils/validators.ts
@@ -1428,7 +1471,7 @@ function decodeBase64(base64) {
1428
1471
 
1429
1472
  // src/utils/frameAnalysis.ts
1430
1473
  var DEFAULT_BLUR_THRESHOLD = 100;
1431
- var BLUR_THRESHOLD_MOBILE = 150;
1474
+ var BLUR_THRESHOLD_MOBILE = 60;
1432
1475
  var BACKLIT_RATIO_THRESHOLD = 0.6;
1433
1476
  var LOW_LIGHT_THRESHOLD = 50;
1434
1477
  var MIN_FACE_TOP_MARGIN = 0.1;
@@ -1437,15 +1480,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1437
1480
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1438
1481
  var HIGH_ALIGNMENT = 0.85;
1439
1482
  var GOOD_ALIGNMENT = 0.5;
1440
- var IDEAL_CROP_MULTIPLIER = 2;
1441
- var MIN_CROP_MULTIPLIER = 1.8;
1442
- var MAX_CROP_MULTIPLIER = 2.5;
1483
+ var IDEAL_CROP_MULTIPLIER = 3;
1484
+ var MIN_CROP_MULTIPLIER = 2.5;
1485
+ var MAX_CROP_MULTIPLIER = 4;
1443
1486
  var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1487
+ var MIN_IDEAL_FACE_RATIO = 0.05;
1488
+ var MAX_IDEAL_FACE_RATIO = 0.2;
1444
1489
  var MIN_FACE_RATIO = 0.036;
1445
1490
  var MAX_FACE_RATIO = 0.7;
1446
1491
  var FACE_CROP_OUTPUT_SIZE = 224;
1447
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1448
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1492
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
1493
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
1449
1494
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1450
1495
  const laplacian = [];
1451
1496
  for (let y = 1; y < height - 1; y++) {
@@ -1531,14 +1576,21 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
1531
1576
  }
1532
1577
  return { visible: true };
1533
1578
  }
1534
- var DEFAULT_OVAL_REGION = {
1579
+ var OVAL_REGION_DESKTOP = {
1535
1580
  centerX: 0.5,
1536
1581
  centerY: 0.5,
1537
1582
  width: 0.36,
1538
- // 36% of frame width (+20%)
1539
1583
  height: 0.48
1540
- // 36% * (4/3) = 48% of frame height (+20%)
1584
+ // 0.36 * (4/3)
1541
1585
  };
1586
+ var OVAL_REGION_MOBILE = {
1587
+ centerX: 0.5,
1588
+ centerY: 0.5,
1589
+ width: 0.48,
1590
+ height: 0.64
1591
+ // 0.48 * (4/3)
1592
+ };
1593
+ var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
1542
1594
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
1543
1595
  const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
1544
1596
  const faceCenterY = (faceBox.originY + faceBox.height / 2) / frameHeight;
@@ -1692,6 +1744,33 @@ function checkFrameQuality(options) {
1692
1744
  }
1693
1745
  return result;
1694
1746
  }
1747
+ var MAX_FACE_ROLL_DEGREES = 15;
1748
+ function detectFaceRoll(landmarks) {
1749
+ if (landmarks.length < 364) {
1750
+ return { roll: 0, tooTilted: false };
1751
+ }
1752
+ const l33 = landmarks[33];
1753
+ const l133 = landmarks[133];
1754
+ const l362 = landmarks[362];
1755
+ const l263 = landmarks[263];
1756
+ if (!l33 || !l133 || !l362 || !l263) {
1757
+ return { roll: 0, tooTilted: false };
1758
+ }
1759
+ const leftEyeX = (l33.x + l133.x) / 2;
1760
+ const leftEyeY = (l33.y + l133.y) / 2;
1761
+ const rightEyeX = (l362.x + l263.x) / 2;
1762
+ const rightEyeY = (l362.y + l263.y) / 2;
1763
+ const eyeXDist = Math.abs(leftEyeX - rightEyeX);
1764
+ const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
1765
+ if (eyeXDist < 0.01) {
1766
+ return { roll: 0, tooTilted: false };
1767
+ }
1768
+ const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
1769
+ return {
1770
+ roll,
1771
+ tooTilted: roll > MAX_FACE_ROLL_DEGREES
1772
+ };
1773
+ }
1695
1774
  var BaseFrameCollector = class {
1696
1775
  constructor(maxFrames = 10) {
1697
1776
  this.frames = [];
@@ -1952,16 +2031,21 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1952
2031
  MAX_CROP_MULTIPLIER,
1953
2032
  MAX_FACE_PERCENTAGE_IN_CROP,
1954
2033
  MAX_FACE_RATIO,
2034
+ MAX_FACE_ROLL_DEGREES,
2035
+ MAX_IDEAL_FACE_RATIO,
1955
2036
  MIN_CAPTURE_ALIGNMENT,
1956
2037
  MIN_CROP_MULTIPLIER,
1957
2038
  MIN_FACE_BOTTOM_MARGIN,
1958
2039
  MIN_FACE_RATIO,
1959
2040
  MIN_FACE_SIDE_MARGIN,
1960
2041
  MIN_FACE_TOP_MARGIN,
2042
+ MIN_IDEAL_FACE_RATIO,
1961
2043
  MIN_LANDMARK_COUNT,
1962
2044
  MODEL_CONFIGS,
1963
2045
  OVAL_GUIDE_COLORS,
1964
2046
  OVAL_GUIDE_STYLES,
2047
+ OVAL_REGION_DESKTOP,
2048
+ OVAL_REGION_MOBILE,
1965
2049
  RETRY_CONFIG,
1966
2050
  TARGET_FACE_PERCENTAGE_IN_CROP,
1967
2051
  analyzeBlur,
@@ -1976,6 +2060,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
1976
2060
  checkEyeRegionQuality,
1977
2061
  checkFrameQuality,
1978
2062
  decodeBase64,
2063
+ detectFaceRoll,
1979
2064
  detectSpecularHighlights,
1980
2065
  encodeBase64,
1981
2066
  generateSessionId,
package/dist/index.mjs CHANGED
@@ -1048,9 +1048,9 @@ function isRetryableError(code) {
1048
1048
  }
1049
1049
 
1050
1050
  // src/constants/feedback.ts
1051
- var ALIGNMENT_THRESHOLD_CAPTURE = 0.6;
1052
- var ALIGNMENT_THRESHOLD_POOR = 0.5;
1053
- var ALIGNMENT_THRESHOLD_GOOD = 0.5;
1051
+ var ALIGNMENT_THRESHOLD_CAPTURE = 0.85;
1052
+ var ALIGNMENT_THRESHOLD_POOR = 0.6;
1053
+ var ALIGNMENT_THRESHOLD_GOOD = 0.6;
1054
1054
  var ALIGNMENT_THRESHOLD_PERFECT = 0.85;
1055
1055
  var OVAL_GUIDE_COLORS = {
1056
1056
  no_face: "#ef4444",
@@ -1101,6 +1101,8 @@ var FEEDBACK_MESSAGES = {
1101
1101
  move_back: "Move back - face too close",
1102
1102
  too_close: "Move back - face too close",
1103
1103
  too_far: "Move closer - face too far",
1104
+ move_closer_ideal: "Move a little closer",
1105
+ move_back_ideal: "Move back slightly",
1104
1106
  // Visibility issues
1105
1107
  face_not_visible: "Center your face - edges cut off",
1106
1108
  partial_face: "Center your face - edges cut off",
@@ -1111,6 +1113,9 @@ var FEEDBACK_MESSAGES = {
1111
1113
  poor_lighting: "Improve lighting",
1112
1114
  too_dark: "Low lighting - move to a brighter area",
1113
1115
  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",
1114
1119
  // Hand occlusion
1115
1120
  hand_detected: "Remove hand from face",
1116
1121
  // Eye region quality
@@ -1158,6 +1163,8 @@ var ES_LOCALE = {
1158
1163
  move_back: "Al\xE9jate - rostro muy cerca",
1159
1164
  too_close: "Al\xE9jate - rostro muy cerca",
1160
1165
  too_far: "Ac\xE9rcate - rostro muy lejos",
1166
+ move_closer_ideal: "Ac\xE9rcate un poco",
1167
+ move_back_ideal: "Al\xE9jate un poco",
1161
1168
  // Visibility issues
1162
1169
  face_not_visible: "Centra tu rostro - bordes cortados",
1163
1170
  partial_face: "Centra tu rostro - bordes cortados",
@@ -1168,6 +1175,9 @@ var ES_LOCALE = {
1168
1175
  poor_lighting: "Mejora la iluminaci\xF3n",
1169
1176
  too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
1170
1177
  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",
1171
1181
  // Hand occlusion
1172
1182
  hand_detected: "Retira la mano del rostro",
1173
1183
  // Eye region quality
@@ -1200,11 +1210,21 @@ function getCaptureQualityFeedback(state) {
1200
1210
  isBlurry,
1201
1211
  isPartialFace,
1202
1212
  framesCaptured,
1203
- targetFrames
1213
+ targetFrames,
1214
+ tooFarFromIdeal,
1215
+ tooCloseToIdeal,
1216
+ phoneAngled,
1217
+ phoneTilted
1204
1218
  } = state;
1205
1219
  if (!hasFace) {
1206
1220
  return FEEDBACK_MESSAGES.no_face;
1207
1221
  }
1222
+ if (phoneTilted) {
1223
+ return FEEDBACK_MESSAGES.phone_tilted;
1224
+ }
1225
+ if (phoneAngled) {
1226
+ return FEEDBACK_MESSAGES.phone_angle_low;
1227
+ }
1208
1228
  if (tooClose) {
1209
1229
  return FEEDBACK_MESSAGES.too_close;
1210
1230
  }
@@ -1217,6 +1237,12 @@ function getCaptureQualityFeedback(state) {
1217
1237
  if (isBlurry) {
1218
1238
  return FEEDBACK_MESSAGES.blurry;
1219
1239
  }
1240
+ if (tooFarFromIdeal) {
1241
+ return FEEDBACK_MESSAGES.move_closer_ideal;
1242
+ }
1243
+ if (tooCloseToIdeal) {
1244
+ return FEEDBACK_MESSAGES.move_back_ideal;
1245
+ }
1220
1246
  if (alignment < ALIGNMENT_THRESHOLD_POOR) {
1221
1247
  return FEEDBACK_MESSAGES.move_closer_to_center;
1222
1248
  }
@@ -1232,8 +1258,19 @@ function getCaptureQualityFeedback(state) {
1232
1258
  return FEEDBACK_MESSAGES.perfect;
1233
1259
  }
1234
1260
  function canCaptureFrame(state) {
1235
- const { hasFace, alignment, tooClose, tooFar, isBlurry, isPartialFace } = state;
1236
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace;
1261
+ const {
1262
+ hasFace,
1263
+ alignment,
1264
+ tooClose,
1265
+ tooFar,
1266
+ isBlurry,
1267
+ isPartialFace,
1268
+ tooFarFromIdeal,
1269
+ tooCloseToIdeal,
1270
+ phoneAngled,
1271
+ phoneTilted
1272
+ } = state;
1273
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !phoneAngled && !phoneTilted;
1237
1274
  }
1238
1275
 
1239
1276
  // src/utils/validators.ts
@@ -1301,7 +1338,7 @@ function decodeBase64(base64) {
1301
1338
 
1302
1339
  // src/utils/frameAnalysis.ts
1303
1340
  var DEFAULT_BLUR_THRESHOLD = 100;
1304
- var BLUR_THRESHOLD_MOBILE = 150;
1341
+ var BLUR_THRESHOLD_MOBILE = 60;
1305
1342
  var BACKLIT_RATIO_THRESHOLD = 0.6;
1306
1343
  var LOW_LIGHT_THRESHOLD = 50;
1307
1344
  var MIN_FACE_TOP_MARGIN = 0.1;
@@ -1310,15 +1347,17 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1310
1347
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1311
1348
  var HIGH_ALIGNMENT = 0.85;
1312
1349
  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;
1350
+ var IDEAL_CROP_MULTIPLIER = 3;
1351
+ var MIN_CROP_MULTIPLIER = 2.5;
1352
+ var MAX_CROP_MULTIPLIER = 4;
1316
1353
  var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1354
+ var MIN_IDEAL_FACE_RATIO = 0.05;
1355
+ var MAX_IDEAL_FACE_RATIO = 0.2;
1317
1356
  var MIN_FACE_RATIO = 0.036;
1318
1357
  var MAX_FACE_RATIO = 0.7;
1319
1358
  var FACE_CROP_OUTPUT_SIZE = 224;
1320
- var MAX_FACE_PERCENTAGE_IN_CROP = 0.6;
1321
- var TARGET_FACE_PERCENTAGE_IN_CROP = 0.5;
1359
+ var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
1360
+ var TARGET_FACE_PERCENTAGE_IN_CROP = 0.33;
1322
1361
  function analyzeBlur(grayscalePixels, width, height, threshold = DEFAULT_BLUR_THRESHOLD) {
1323
1362
  const laplacian = [];
1324
1363
  for (let y = 1; y < height - 1; y++) {
@@ -1404,14 +1443,21 @@ function isFaceFullyVisible(boundingBox, frameWidth, frameHeight) {
1404
1443
  }
1405
1444
  return { visible: true };
1406
1445
  }
1407
- var DEFAULT_OVAL_REGION = {
1446
+ var OVAL_REGION_DESKTOP = {
1408
1447
  centerX: 0.5,
1409
1448
  centerY: 0.5,
1410
1449
  width: 0.36,
1411
- // 36% of frame width (+20%)
1412
1450
  height: 0.48
1413
- // 36% * (4/3) = 48% of frame height (+20%)
1451
+ // 0.36 * (4/3)
1414
1452
  };
1453
+ var OVAL_REGION_MOBILE = {
1454
+ centerX: 0.5,
1455
+ centerY: 0.5,
1456
+ width: 0.48,
1457
+ height: 0.64
1458
+ // 0.48 * (4/3)
1459
+ };
1460
+ var DEFAULT_OVAL_REGION = OVAL_REGION_DESKTOP;
1415
1461
  function isFaceInOval(faceBox, frameWidth, frameHeight, oval = DEFAULT_OVAL_REGION, tolerance = 0.3) {
1416
1462
  const faceCenterX = (faceBox.originX + faceBox.width / 2) / frameWidth;
1417
1463
  const faceCenterY = (faceBox.originY + faceBox.height / 2) / frameHeight;
@@ -1565,6 +1611,33 @@ function checkFrameQuality(options) {
1565
1611
  }
1566
1612
  return result;
1567
1613
  }
1614
+ var MAX_FACE_ROLL_DEGREES = 15;
1615
+ function detectFaceRoll(landmarks) {
1616
+ if (landmarks.length < 364) {
1617
+ return { roll: 0, tooTilted: false };
1618
+ }
1619
+ const l33 = landmarks[33];
1620
+ const l133 = landmarks[133];
1621
+ const l362 = landmarks[362];
1622
+ const l263 = landmarks[263];
1623
+ if (!l33 || !l133 || !l362 || !l263) {
1624
+ return { roll: 0, tooTilted: false };
1625
+ }
1626
+ const leftEyeX = (l33.x + l133.x) / 2;
1627
+ const leftEyeY = (l33.y + l133.y) / 2;
1628
+ const rightEyeX = (l362.x + l263.x) / 2;
1629
+ const rightEyeY = (l362.y + l263.y) / 2;
1630
+ const eyeXDist = Math.abs(leftEyeX - rightEyeX);
1631
+ const eyeYDiff = Math.abs(leftEyeY - rightEyeY);
1632
+ if (eyeXDist < 0.01) {
1633
+ return { roll: 0, tooTilted: false };
1634
+ }
1635
+ const roll = Math.atan2(eyeYDiff, eyeXDist) * (180 / Math.PI);
1636
+ return {
1637
+ roll,
1638
+ tooTilted: roll > MAX_FACE_ROLL_DEGREES
1639
+ };
1640
+ }
1568
1641
  var BaseFrameCollector = class {
1569
1642
  constructor(maxFrames = 10) {
1570
1643
  this.frames = [];
@@ -1824,16 +1897,21 @@ export {
1824
1897
  MAX_CROP_MULTIPLIER,
1825
1898
  MAX_FACE_PERCENTAGE_IN_CROP,
1826
1899
  MAX_FACE_RATIO,
1900
+ MAX_FACE_ROLL_DEGREES,
1901
+ MAX_IDEAL_FACE_RATIO,
1827
1902
  MIN_CAPTURE_ALIGNMENT,
1828
1903
  MIN_CROP_MULTIPLIER,
1829
1904
  MIN_FACE_BOTTOM_MARGIN,
1830
1905
  MIN_FACE_RATIO,
1831
1906
  MIN_FACE_SIDE_MARGIN,
1832
1907
  MIN_FACE_TOP_MARGIN,
1908
+ MIN_IDEAL_FACE_RATIO,
1833
1909
  MIN_LANDMARK_COUNT,
1834
1910
  MODEL_CONFIGS,
1835
1911
  OVAL_GUIDE_COLORS,
1836
1912
  OVAL_GUIDE_STYLES,
1913
+ OVAL_REGION_DESKTOP,
1914
+ OVAL_REGION_MOBILE,
1837
1915
  RETRY_CONFIG,
1838
1916
  TARGET_FACE_PERCENTAGE_IN_CROP,
1839
1917
  analyzeBlur,
@@ -1848,6 +1926,7 @@ export {
1848
1926
  checkEyeRegionQuality,
1849
1927
  checkFrameQuality,
1850
1928
  decodeBase64,
1929
+ detectFaceRoll,
1851
1930
  detectSpecularHighlights,
1852
1931
  encodeBase64,
1853
1932
  generateSessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.4.0",
3
+ "version": "2.6.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",