@moveris/shared 3.8.1 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,6 +56,8 @@ const client = new LivenessClient(config: LivenessClientConfig);
56
56
  | `enableRetry` | `boolean` | `true` | Enable automatic retry with exponential backoff |
57
57
  | `customFetch` | `typeof fetch` | `fetch` | Custom fetch implementation (for React Native) |
58
58
  | `deviceIntelligenceOverrides` | `DeviceIntelligenceOverrides` | - | Static overrides merged into every `device_intelligence` payload (e.g. `{ vpn_detected: true }`) |
59
+ | `consumer` | `ConsumerContext` | - | Consumer app context — traces which app and environment submitted each transaction |
60
+ | `trackClientTime` | `boolean` | `false` | When true, fires a fire-and-forget PATCH after each verdict to record end-to-end client duration |
59
61
 
60
62
  #### Methods
61
63
 
@@ -152,6 +154,17 @@ const result = await client.hybrid50(frames, { fps: 30 });
152
154
  const result = await client.hybrid150(frames, { fps: 30 });
153
155
  ```
154
156
 
157
+ ##### `postClientTime(sessionId, clientTime)`
158
+
159
+ Fire-and-forget PATCH to record end-to-end client duration for a completed verification session. No-op when `trackClientTime` is `false`. Swallows all errors silently.
160
+
161
+ ```typescript
162
+ // Called automatically by useLiveness / CognitoCheckWidget after the verdict.
163
+ // Approach B consumers can call it manually after onComplete resolves:
164
+ const elapsed = Math.round(performance.now() - startTime);
165
+ void client.postClientTime(result.sessionId, elapsed);
166
+ ```
167
+
155
168
  ##### `updateDeviceIntelligenceOverrides(overrides)`
156
169
 
157
170
  Merge additional fields into the cached device intelligence payload. Call this after construction to inject data only available at runtime (e.g. camera specs, VPN detection results). Merges shallowly — later calls are merged on top of earlier ones.
@@ -254,6 +267,27 @@ Deprecated models (v1 — sunset 2026-09-01):
254
267
  | `'mixed-150'` | 150 | — |
255
268
  | `'mixed-250'` | 250 | — |
256
269
 
270
+ #### ConsumerContext
271
+
272
+ Consumer app context attached to every verification request for transaction tracing.
273
+
274
+ ```typescript
275
+ interface ConsumerContext {
276
+ url: string; // The consumer app's URL
277
+ env: 'development' | 'staging' | 'production'; // Consumer app environment
278
+ }
279
+ ```
280
+
281
+ Pass it at client construction — it is included in every subsequent request automatically:
282
+
283
+ ```typescript
284
+ const client = new LivenessClient({
285
+ apiKey: 'mv_your_api_key',
286
+ consumer: { url: 'https://app.example.com', env: 'production' },
287
+ trackClientTime: true, // optional — fires postClientTime after each verdict
288
+ });
289
+ ```
290
+
257
291
  #### FrameSource
258
292
 
259
293
  Source of the captured frames.
@@ -308,6 +342,7 @@ interface LivenessResult {
308
342
  processingMs: number; // Server processing time
309
343
  framesProcessed: number; // Number of frames analyzed
310
344
  deprecation?: DeprecationInfo; // Present when X-Moveris-Model-Resolved header is returned
345
+ clientTime?: number; // End-to-end client duration in ms (start() to verdict received)
311
346
  }
312
347
  ```
313
348
 
@@ -669,14 +704,13 @@ const inOval = isFaceInOval(faceBbox, ovalRegion);
669
704
  console.log(inOval.isInOval); // true/false
670
705
  console.log(inOval.alignmentScore); // 0-1
671
706
 
672
- // Calculate crop region for face (224x224, aligned with cognito-check constants)
707
+ // Calculate crop region for face frame-relative 20% margin on each side,
708
+ // matches the API server-side crop and the model training pipeline.
673
709
  const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
674
- // { x, y, size } — square crop region in pixel coordinates
675
-
676
- // Calculate adaptive crop multiplier based on face size
677
- import { calculateAdaptiveCropMultiplier } from '@moveris/shared';
678
- const multiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
679
- // Returns 2.5–4.0x (matched to cognito-check for ~33% face coverage in 224×224 crop)
710
+ // { x, y, width, height } — possibly non-square rectangle in source pixel
711
+ // coordinates. Callers must resize the cropped pixels to 224×224 with
712
+ // bilinear interpolation; non-square crops are squashed (intentional, matches
713
+ // cv2.resize behaviour applied server-side).
680
714
 
681
715
  // Detect face roll (device tilt) — prefer matrix when available (more accurate under glasses/occlusion)
682
716
  if (facialTransformationMatrix) {
@@ -700,17 +734,21 @@ const drResult = analyzeDynamicRange(rgbaPixels);
700
734
  // Soft warning — do not use as a hard capture gate
701
735
  ```
702
736
 
703
- #### Crop Constants (aligned with cognito-check)
737
+ #### Crop Constants (aligned with the model contract)
738
+
739
+ These constants control how face crops are generated for the `fast-check-crops` endpoint. They match the algorithm applied server-side in `m-ai-check-api` and during model training:
704
740
 
705
- These constants control how face crops are generated for the `fast-check-crops` endpoint:
741
+ 1. Detect the face. Get the bounding box in source pixel coordinates.
742
+ 2. Expand the bbox by `FACE_CROP_FRAME_MARGIN × frameWidth/Height` on each side.
743
+ 3. Clamp the resulting rectangle to `[0, frameWidth] × [0, frameHeight]`. The crop is **not necessarily square**.
744
+ 4. Resize the cropped pixels to exactly `FACE_CROP_OUTPUT_SIZE × FACE_CROP_OUTPUT_SIZE` with bilinear interpolation. Non-square crops are squashed (matches `cv2.resize` server-side).
706
745
 
707
- | Constant | Value | Description |
708
- | -------------------------------- | ----- | ------------------------------------------------ |
709
- | `IDEAL_CROP_MULTIPLIER` | 2.0 | Default crop region = 2x face size |
710
- | `MIN_CROP_MULTIPLIER` | 1.8 | Minimum crop (face very close) |
711
- | `MAX_CROP_MULTIPLIER` | 2.5 | Maximum crop (face very far) |
712
- | `FACE_CENTER_VERTICAL_OFFSET` | 0.05 | Slight upward shift to include forehead for rPPG |
713
- | `TARGET_FACE_PERCENTAGE_IN_CROP` | 0.5 | Face should occupy ~50% of 224x224 crop |
746
+ | Constant | Value | Description |
747
+ | -------------------------------- | ------ | --------------------------------------------------------------------- |
748
+ | `FACE_CROP_FRAME_MARGIN` | `0.2` | Margin around the face bbox, as a fraction of frame W/H, on each side |
749
+ | `FACE_CROP_OUTPUT_SIZE` | `224` | Required output size for the cropped face image (px) |
750
+ | `MAX_FACE_PERCENTAGE_IN_CROP` | `0.45` | UX guard — nudge users back when the face dominates the crop |
751
+ | `TARGET_FACE_PERCENTAGE_IN_CROP` | `0.33` | UX-only target framing; not a model contract |
714
752
 
715
753
  ---
716
754
 
@@ -853,6 +891,7 @@ import type {
853
891
  Verdict,
854
892
  CapturedFrame,
855
893
  CropData,
894
+ ConsumerContext,
856
895
  LivenessClientConfig,
857
896
  DetectionResult,
858
897
  DetectionSummary,
package/dist/index.d.mts CHANGED
@@ -26,6 +26,10 @@ type DeviceIntelligenceOverrides = {
26
26
  };
27
27
 
28
28
  type Verdict = 'live' | 'fake' | 'inconclusive';
29
+ interface ConsumerContext {
30
+ url: string;
31
+ env: 'development' | 'staging' | 'production';
32
+ }
29
33
  interface ModelEntry {
30
34
  id: string;
31
35
  label: string;
@@ -68,6 +72,9 @@ interface FastCheckRequest {
68
72
  frame_count?: number;
69
73
  warnings?: string[];
70
74
  device_intelligence?: DeviceIntelligence;
75
+ metadata?: {
76
+ consumer?: ConsumerContext;
77
+ } | null;
71
78
  }
72
79
  interface FastCheckCropsRequest {
73
80
  session_id: string;
@@ -76,6 +83,9 @@ interface FastCheckCropsRequest {
76
83
  crops: CropData[];
77
84
  frame_count?: number;
78
85
  device_intelligence?: DeviceIntelligence;
86
+ metadata?: {
87
+ consumer?: ConsumerContext;
88
+ } | null;
79
89
  }
80
90
  interface VerifyRequest {
81
91
  session_id: string;
@@ -108,6 +118,9 @@ interface FastCheckStreamRequest {
108
118
  frame_count?: number;
109
119
  warnings?: string[];
110
120
  device_intelligence?: DeviceIntelligence;
121
+ metadata?: {
122
+ consumer?: ConsumerContext;
123
+ } | null;
111
124
  }
112
125
  interface FastCheckResponse {
113
126
  verdict: Verdict | null;
@@ -208,6 +221,7 @@ interface LivenessResult {
208
221
  framesProcessed: number;
209
222
  warnings?: string[];
210
223
  deprecation?: DeprecationInfo;
224
+ clientTime?: number;
211
225
  }
212
226
  type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
213
227
  interface LivenessConfig {
@@ -362,10 +376,7 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
362
376
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
363
377
  declare const HIGH_ALIGNMENT = 0.85;
364
378
  declare const GOOD_ALIGNMENT = 0.5;
365
- declare const IDEAL_CROP_MULTIPLIER = 3;
366
- declare const MIN_CROP_MULTIPLIER = 2.5;
367
- declare const MAX_CROP_MULTIPLIER = 4;
368
- declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
379
+ declare const FACE_CROP_FRAME_MARGIN = 0.2;
369
380
  declare const MIN_IDEAL_FACE_RATIO = 0.05;
370
381
  declare const MAX_IDEAL_FACE_RATIO = 0.2;
371
382
  declare const MIN_FACE_RATIO = 0.036;
@@ -392,12 +403,12 @@ declare const OVAL_REGION_MOBILE: OvalRegion;
392
403
  declare const DEFAULT_OVAL_REGION: OvalRegion;
393
404
  declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
394
405
  declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
395
- declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
396
406
  declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
397
407
  declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
398
408
  x: number;
399
409
  y: number;
400
- size: number;
410
+ width: number;
411
+ height: number;
401
412
  };
402
413
  declare function checkFrameQuality(options: {
403
414
  faceBox?: FaceBoundingBox;
@@ -570,6 +581,8 @@ interface LivenessClientConfig {
570
581
  enableRetry?: boolean;
571
582
  customFetch?: typeof fetch;
572
583
  deviceIntelligenceOverrides?: DeviceIntelligenceOverrides;
584
+ consumer?: ConsumerContext;
585
+ trackClientTime?: boolean;
573
586
  }
574
587
  declare class LivenessApiError extends Error {
575
588
  readonly code: string;
@@ -590,6 +603,8 @@ declare class LivenessClient {
590
603
  private readonly timeout;
591
604
  private readonly enableRetry;
592
605
  private readonly fetchFn;
606
+ private readonly consumer;
607
+ private readonly trackClientTime;
593
608
  private diCollected;
594
609
  private diCollecting;
595
610
  private diOverrides;
@@ -610,6 +625,7 @@ declare class LivenessClient {
610
625
  health(): Promise<HealthResponse>;
611
626
  getModels(): Promise<ModelEntry[]>;
612
627
  queueStats(): Promise<QueueStatsResponse>;
628
+ postClientTime(sessionId: string, clientTime: number): Promise<void>;
613
629
  fastCheck(frames: CapturedFrame[], options?: {
614
630
  sessionId?: string;
615
631
  model?: FastCheckModel;
@@ -731,6 +747,7 @@ declare const API_PATHS: {
731
747
  readonly hybrid150: "/api/v1/hybrid-150";
732
748
  readonly jobResult: "/api/v1/result";
733
749
  readonly queueStats: "/api/v1/queue/stats";
750
+ readonly sessions: "/api/v1/sessions";
734
751
  };
735
752
  declare const RETRY_CONFIG: {
736
753
  readonly maxAttempts: 3;
@@ -945,4 +962,4 @@ declare function collectDeviceIntelligence(opts?: {
945
962
  platformVersion?: string;
946
963
  }): Promise<DeviceIntelligence | null>;
947
964
 
948
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, type CameraCapabilities, type CameraRequirements, type CameraValidationResult, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_CAMERA_REQUIREMENTS, 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, DYNAMIC_RANGE_WARNING_THRESHOLD, type DeprecationInfo, type DetectionResult, type DetectionSummary, type DetectorConfig, type DeviceIntelligence, type DeviceIntelligenceCamera, type DeviceIntelligenceGeo, type DeviceIntelligenceOverrides, type DynamicRangeAnalysis, 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_AREA_RATIO, 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 ModelVersion, 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, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
965
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, type CameraCapabilities, type CameraRequirements, type CameraValidationResult, type CaptureQualityState, type CapturedFrame, type ConsumerContext, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_CAMERA_REQUIREMENTS, 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, DYNAMIC_RANGE_WARNING_THRESHOLD, type DeprecationInfo, type DetectionResult, type DetectionSummary, type DetectorConfig, type DeviceIntelligence, type DeviceIntelligenceCamera, type DeviceIntelligenceGeo, type DeviceIntelligenceOverrides, type DynamicRangeAnalysis, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, FACE_CROP_FRAME_MARGIN, 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, 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_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_FACE_AREA_RATIO, 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 ModelVersion, 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, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.d.ts CHANGED
@@ -26,6 +26,10 @@ type DeviceIntelligenceOverrides = {
26
26
  };
27
27
 
28
28
  type Verdict = 'live' | 'fake' | 'inconclusive';
29
+ interface ConsumerContext {
30
+ url: string;
31
+ env: 'development' | 'staging' | 'production';
32
+ }
29
33
  interface ModelEntry {
30
34
  id: string;
31
35
  label: string;
@@ -68,6 +72,9 @@ interface FastCheckRequest {
68
72
  frame_count?: number;
69
73
  warnings?: string[];
70
74
  device_intelligence?: DeviceIntelligence;
75
+ metadata?: {
76
+ consumer?: ConsumerContext;
77
+ } | null;
71
78
  }
72
79
  interface FastCheckCropsRequest {
73
80
  session_id: string;
@@ -76,6 +83,9 @@ interface FastCheckCropsRequest {
76
83
  crops: CropData[];
77
84
  frame_count?: number;
78
85
  device_intelligence?: DeviceIntelligence;
86
+ metadata?: {
87
+ consumer?: ConsumerContext;
88
+ } | null;
79
89
  }
80
90
  interface VerifyRequest {
81
91
  session_id: string;
@@ -108,6 +118,9 @@ interface FastCheckStreamRequest {
108
118
  frame_count?: number;
109
119
  warnings?: string[];
110
120
  device_intelligence?: DeviceIntelligence;
121
+ metadata?: {
122
+ consumer?: ConsumerContext;
123
+ } | null;
111
124
  }
112
125
  interface FastCheckResponse {
113
126
  verdict: Verdict | null;
@@ -208,6 +221,7 @@ interface LivenessResult {
208
221
  framesProcessed: number;
209
222
  warnings?: string[];
210
223
  deprecation?: DeprecationInfo;
224
+ clientTime?: number;
211
225
  }
212
226
  type LivenessState = 'idle' | 'capturing' | 'uploading' | 'processing' | 'complete' | 'error';
213
227
  interface LivenessConfig {
@@ -362,10 +376,7 @@ declare const MIN_FACE_SIDE_MARGIN = 0.05;
362
376
  declare const MIN_CAPTURE_ALIGNMENT = 0.6;
363
377
  declare const HIGH_ALIGNMENT = 0.85;
364
378
  declare const GOOD_ALIGNMENT = 0.5;
365
- declare const IDEAL_CROP_MULTIPLIER = 3;
366
- declare const MIN_CROP_MULTIPLIER = 2.5;
367
- declare const MAX_CROP_MULTIPLIER = 4;
368
- declare const FACE_CENTER_VERTICAL_OFFSET = 0.05;
379
+ declare const FACE_CROP_FRAME_MARGIN = 0.2;
369
380
  declare const MIN_IDEAL_FACE_RATIO = 0.05;
370
381
  declare const MAX_IDEAL_FACE_RATIO = 0.2;
371
382
  declare const MIN_FACE_RATIO = 0.036;
@@ -392,12 +403,12 @@ declare const OVAL_REGION_MOBILE: OvalRegion;
392
403
  declare const DEFAULT_OVAL_REGION: OvalRegion;
393
404
  declare function isFaceInOval(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number, oval?: OvalRegion, tolerance?: number): FaceInOvalResult;
394
405
  declare function calculateFaceAlignment(boundingBox: FaceBoundingBox, frameWidth: number, frameHeight: number): FaceAlignmentResult;
395
- declare function calculateAdaptiveCropMultiplier(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): number;
396
406
  declare function isFaceCropFullyInFrame(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): boolean;
397
407
  declare function calculateFaceCropRegion(faceBox: FaceBoundingBox, frameWidth: number, frameHeight: number): {
398
408
  x: number;
399
409
  y: number;
400
- size: number;
410
+ width: number;
411
+ height: number;
401
412
  };
402
413
  declare function checkFrameQuality(options: {
403
414
  faceBox?: FaceBoundingBox;
@@ -570,6 +581,8 @@ interface LivenessClientConfig {
570
581
  enableRetry?: boolean;
571
582
  customFetch?: typeof fetch;
572
583
  deviceIntelligenceOverrides?: DeviceIntelligenceOverrides;
584
+ consumer?: ConsumerContext;
585
+ trackClientTime?: boolean;
573
586
  }
574
587
  declare class LivenessApiError extends Error {
575
588
  readonly code: string;
@@ -590,6 +603,8 @@ declare class LivenessClient {
590
603
  private readonly timeout;
591
604
  private readonly enableRetry;
592
605
  private readonly fetchFn;
606
+ private readonly consumer;
607
+ private readonly trackClientTime;
593
608
  private diCollected;
594
609
  private diCollecting;
595
610
  private diOverrides;
@@ -610,6 +625,7 @@ declare class LivenessClient {
610
625
  health(): Promise<HealthResponse>;
611
626
  getModels(): Promise<ModelEntry[]>;
612
627
  queueStats(): Promise<QueueStatsResponse>;
628
+ postClientTime(sessionId: string, clientTime: number): Promise<void>;
613
629
  fastCheck(frames: CapturedFrame[], options?: {
614
630
  sessionId?: string;
615
631
  model?: FastCheckModel;
@@ -731,6 +747,7 @@ declare const API_PATHS: {
731
747
  readonly hybrid150: "/api/v1/hybrid-150";
732
748
  readonly jobResult: "/api/v1/result";
733
749
  readonly queueStats: "/api/v1/queue/stats";
750
+ readonly sessions: "/api/v1/sessions";
734
751
  };
735
752
  declare const RETRY_CONFIG: {
736
753
  readonly maxAttempts: 3;
@@ -945,4 +962,4 @@ declare function collectDeviceIntelligence(opts?: {
945
962
  platformVersion?: string;
946
963
  }): Promise<DeviceIntelligence | null>;
947
964
 
948
- export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, type CameraCapabilities, type CameraRequirements, type CameraValidationResult, type CaptureQualityState, type CapturedFrame, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_CAMERA_REQUIREMENTS, 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, DYNAMIC_RANGE_WARNING_THRESHOLD, type DeprecationInfo, type DetectionResult, type DetectionSummary, type DetectorConfig, type DeviceIntelligence, type DeviceIntelligenceCamera, type DeviceIntelligenceGeo, type DeviceIntelligenceOverrides, type DynamicRangeAnalysis, 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_AREA_RATIO, 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 ModelVersion, 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, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateAdaptiveCropMultiplier, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
965
+ export { ALIGNMENT_THRESHOLD_CAPTURE, ALIGNMENT_THRESHOLD_GOOD, ALIGNMENT_THRESHOLD_PERFECT, ALIGNMENT_THRESHOLD_POOR, API_ENDPOINTS, API_ERROR_CODES, API_PATHS, AUTH_CONFIG, type ApiErrorCode, BACKLIT_RATIO_THRESHOLD, BLUR_THRESHOLD_MOBILE, BaseFrameCollector, type BlurAnalysis, CAMERA_ANGLE_HIGH_RATIO, CAMERA_ANGLE_LOW_RATIO, type CameraAngleResult, type CameraCapabilities, type CameraRequirements, type CameraValidationResult, type CaptureQualityState, type CapturedFrame, type ConsumerContext, type CropData, DEFAULT_BLUR_THRESHOLD, DEFAULT_CAMERA_REQUIREMENTS, 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, DYNAMIC_RANGE_WARNING_THRESHOLD, type DeprecationInfo, type DetectionResult, type DetectionSummary, type DetectorConfig, type DeviceIntelligence, type DeviceIntelligenceCamera, type DeviceIntelligenceGeo, type DeviceIntelligenceOverrides, type DynamicRangeAnalysis, ERROR_MESSAGES, ERROR_MESSAGES_ES, ES_LOCALE, EYE_LANDMARK_INDICES, EYE_QUALITY_THRESHOLDS, type ErrorResponse, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, FACE_CROP_FRAME_MARGIN, 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, 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_FACE_PERCENTAGE_IN_CROP, MAX_FACE_RATIO, MAX_FACE_ROLL_DEGREES, MAX_IDEAL_FACE_RATIO, MIN_CAPTURE_ALIGNMENT, MIN_FACE_AREA_RATIO, 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 ModelVersion, 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, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.js CHANGED
@@ -50,7 +50,7 @@ __export(index_exports, {
50
50
  ES_LOCALE: () => ES_LOCALE,
51
51
  EYE_LANDMARK_INDICES: () => EYE_LANDMARK_INDICES,
52
52
  EYE_QUALITY_THRESHOLDS: () => EYE_QUALITY_THRESHOLDS,
53
- FACE_CENTER_VERTICAL_OFFSET: () => FACE_CENTER_VERTICAL_OFFSET,
53
+ FACE_CROP_FRAME_MARGIN: () => FACE_CROP_FRAME_MARGIN,
54
54
  FACE_CROP_OUTPUT_SIZE: () => FACE_CROP_OUTPUT_SIZE,
55
55
  FEEDBACK_MESSAGES: () => FEEDBACK_MESSAGES,
56
56
  FRAME_BUFFER_CONFIG: () => FRAME_BUFFER_CONFIG,
@@ -60,20 +60,17 @@ __export(index_exports, {
60
60
  GOOD_ALIGNMENT: () => GOOD_ALIGNMENT,
61
61
  HIGH_ALIGNMENT: () => HIGH_ALIGNMENT,
62
62
  HYBRID_MODEL_CONFIGS: () => HYBRID_MODEL_CONFIGS,
63
- IDEAL_CROP_MULTIPLIER: () => IDEAL_CROP_MULTIPLIER,
64
63
  LANDMARK_INDEX: () => LANDMARK_INDEX,
65
64
  LANDMARK_MAX_BOUND: () => LANDMARK_MAX_BOUND,
66
65
  LANDMARK_MIN_BOUND: () => LANDMARK_MIN_BOUND,
67
66
  LOW_LIGHT_THRESHOLD: () => LOW_LIGHT_THRESHOLD,
68
67
  LivenessApiError: () => LivenessApiError,
69
68
  LivenessClient: () => LivenessClient,
70
- MAX_CROP_MULTIPLIER: () => MAX_CROP_MULTIPLIER,
71
69
  MAX_FACE_PERCENTAGE_IN_CROP: () => MAX_FACE_PERCENTAGE_IN_CROP,
72
70
  MAX_FACE_RATIO: () => MAX_FACE_RATIO,
73
71
  MAX_FACE_ROLL_DEGREES: () => MAX_FACE_ROLL_DEGREES,
74
72
  MAX_IDEAL_FACE_RATIO: () => MAX_IDEAL_FACE_RATIO,
75
73
  MIN_CAPTURE_ALIGNMENT: () => MIN_CAPTURE_ALIGNMENT,
76
- MIN_CROP_MULTIPLIER: () => MIN_CROP_MULTIPLIER,
77
74
  MIN_FACE_AREA_RATIO: () => MIN_FACE_AREA_RATIO,
78
75
  MIN_FACE_BOTTOM_MARGIN: () => MIN_FACE_BOTTOM_MARGIN,
79
76
  MIN_FACE_RATIO: () => MIN_FACE_RATIO,
@@ -95,7 +92,6 @@ __export(index_exports, {
95
92
  analyzeEyeRegionBrightness: () => analyzeEyeRegionBrightness,
96
93
  analyzeEyeRegionContrast: () => analyzeEyeRegionContrast,
97
94
  analyzeLighting: () => analyzeLighting,
98
- calculateAdaptiveCropMultiplier: () => calculateAdaptiveCropMultiplier,
99
95
  calculateBrightness: () => calculateBrightness,
100
96
  calculateFaceAlignment: () => calculateFaceAlignment,
101
97
  calculateFaceCropRegion: () => calculateFaceCropRegion,
@@ -162,7 +158,8 @@ var API_PATHS = {
162
158
  hybrid50: "/api/v1/hybrid-50",
163
159
  hybrid150: "/api/v1/hybrid-150",
164
160
  jobResult: "/api/v1/result",
165
- queueStats: "/api/v1/queue/stats"
161
+ queueStats: "/api/v1/queue/stats",
162
+ sessions: "/api/v1/sessions"
166
163
  };
167
164
  var RETRY_CONFIG = {
168
165
  maxAttempts: 3,
@@ -242,7 +239,7 @@ async function sleep(ms) {
242
239
  }
243
240
 
244
241
  // package.json
245
- var version = "3.8.1";
242
+ var version = "3.9.0";
246
243
 
247
244
  // src/utils/deviceIntelligence.ts
248
245
  var IPINFO_URL = "https://ipinfo.io/json";
@@ -390,6 +387,8 @@ var LivenessClient = class _LivenessClient {
390
387
  this.enableRetry = config.enableRetry ?? true;
391
388
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
392
389
  this.diOverrides = { ...config.deviceIntelligenceOverrides };
390
+ this.consumer = config.consumer;
391
+ this.trackClientTime = config.trackClientTime ?? false;
393
392
  }
394
393
  /**
395
394
  * Merge additional device intelligence overrides into the existing set.
@@ -638,6 +637,25 @@ var LivenessClient = class _LivenessClient {
638
637
  async queueStats() {
639
638
  return this.request(API_PATHS.queueStats);
640
639
  }
640
+ /**
641
+ * Post end-to-end client duration for a completed verification session.
642
+ * Fire-and-forget — swallows all errors, no retry.
643
+ * No-op when `trackClientTime` is false (default).
644
+ */
645
+ async postClientTime(sessionId, clientTime) {
646
+ if (!this.trackClientTime) return;
647
+ try {
648
+ await this.fetchFn(`${this.baseUrl}${API_PATHS.sessions}/${sessionId}/client-time`, {
649
+ method: "PATCH",
650
+ headers: {
651
+ "Content-Type": "application/json",
652
+ [AUTH_CONFIG.apiKeyHeader]: this.apiKey
653
+ },
654
+ body: JSON.stringify({ client_time: clientTime })
655
+ });
656
+ } catch {
657
+ }
658
+ }
641
659
  // ===========================================================================
642
660
  // Fast Check Endpoints
643
661
  // ===========================================================================
@@ -658,7 +676,8 @@ var LivenessClient = class _LivenessClient {
658
676
  frames: toFrameData(frames),
659
677
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
660
678
  ...options.warnings?.length ? { warnings: options.warnings } : {},
661
- ...di ? { device_intelligence: di } : {}
679
+ ...di ? { device_intelligence: di } : {},
680
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
662
681
  };
663
682
  const { data: response, headers } = await this.requestWithRetryRaw(
664
683
  API_PATHS.fastCheck,
@@ -695,7 +714,8 @@ var LivenessClient = class _LivenessClient {
695
714
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
696
715
  ...options.warnings?.length ? { warnings: options.warnings } : {},
697
716
  ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {},
698
- ...di ? { device_intelligence: di } : {}
717
+ ...di ? { device_intelligence: di } : {},
718
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
699
719
  };
700
720
  const { data: response, headers } = await this.requestWithRetryRaw(
701
721
  API_PATHS.fastCheckCrops,
@@ -759,7 +779,8 @@ var LivenessClient = class _LivenessClient {
759
779
  frame: frameData,
760
780
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
761
781
  ...options.warnings?.length ? { warnings: options.warnings } : {},
762
- ...di ? { device_intelligence: di } : {}
782
+ ...di ? { device_intelligence: di } : {},
783
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
763
784
  };
764
785
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
765
786
  method: "POST",
@@ -1786,10 +1807,7 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1786
1807
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1787
1808
  var HIGH_ALIGNMENT = 0.85;
1788
1809
  var GOOD_ALIGNMENT = 0.5;
1789
- var IDEAL_CROP_MULTIPLIER = 3;
1790
- var MIN_CROP_MULTIPLIER = 2.5;
1791
- var MAX_CROP_MULTIPLIER = 4;
1792
- var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1810
+ var FACE_CROP_FRAME_MARGIN = 0.2;
1793
1811
  var MIN_IDEAL_FACE_RATIO = 0.05;
1794
1812
  var MAX_IDEAL_FACE_RATIO = 0.2;
1795
1813
  var MIN_FACE_RATIO = 0.036;
@@ -1983,40 +2001,21 @@ function calculateFaceAlignment(boundingBox, frameWidth, frameHeight) {
1983
2001
  tooFar: faceRatio < MIN_FACE_RATIO
1984
2002
  };
1985
2003
  }
1986
- function calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight) {
1987
- const faceSize = Math.max(faceBox.width, faceBox.height);
1988
- const idealCropSize = faceSize * IDEAL_CROP_MULTIPLIER;
1989
- const maxCropSize = Math.min(frameWidth, frameHeight);
1990
- if (idealCropSize <= maxCropSize) {
1991
- return IDEAL_CROP_MULTIPLIER;
1992
- }
1993
- const adaptiveMultiplier = maxCropSize / faceSize;
1994
- return Math.max(MIN_CROP_MULTIPLIER, Math.min(MAX_CROP_MULTIPLIER, adaptiveMultiplier));
1995
- }
1996
2004
  function isFaceCropFullyInFrame(faceBox, frameWidth, frameHeight) {
1997
- const marginMultiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
1998
- const marginX = faceBox.width * (marginMultiplier - 1) / 2;
1999
- const marginY = faceBox.height * (marginMultiplier - 1) / 2;
2000
- const faceLeft = faceBox.originX - marginX;
2001
- const faceTop = faceBox.originY - marginY;
2002
- const faceRight = faceBox.originX + faceBox.width + marginX;
2003
- const faceBottom = faceBox.originY + faceBox.height + marginY;
2005
+ const faceLeft = faceBox.originX;
2006
+ const faceTop = faceBox.originY;
2007
+ const faceRight = faceBox.originX + faceBox.width;
2008
+ const faceBottom = faceBox.originY + faceBox.height;
2004
2009
  return faceLeft >= 0 && faceTop >= 0 && faceRight <= frameWidth && faceBottom <= frameHeight;
2005
2010
  }
2006
2011
  function calculateFaceCropRegion(faceBox, frameWidth, frameHeight) {
2007
- const cropMultiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
2008
- const expandedWidth = faceBox.width * cropMultiplier;
2009
- const expandedHeight = faceBox.height * cropMultiplier;
2010
- const cropSize = Math.max(expandedWidth, expandedHeight);
2011
- const centerX = faceBox.originX + faceBox.width / 2;
2012
- const verticalShift = faceBox.height * FACE_CENTER_VERTICAL_OFFSET;
2013
- const centerY = faceBox.originY + faceBox.height / 2 + verticalShift;
2014
- let cropX = centerX - cropSize / 2;
2015
- let cropY = centerY - cropSize / 2;
2016
- cropX = Math.max(0, Math.min(frameWidth - cropSize, cropX));
2017
- cropY = Math.max(0, Math.min(frameHeight - cropSize, cropY));
2018
- const finalSize = Math.min(cropSize, frameWidth - cropX, frameHeight - cropY);
2019
- return { x: cropX, y: cropY, size: finalSize };
2012
+ const marginX = FACE_CROP_FRAME_MARGIN * frameWidth;
2013
+ const marginY = FACE_CROP_FRAME_MARGIN * frameHeight;
2014
+ const x1 = Math.max(0, faceBox.originX - marginX);
2015
+ const y1 = Math.max(0, faceBox.originY - marginY);
2016
+ const x2 = Math.min(frameWidth, faceBox.originX + faceBox.width + marginX);
2017
+ const y2 = Math.min(frameHeight, faceBox.originY + faceBox.height + marginY);
2018
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
2020
2019
  }
2021
2020
  function checkFrameQuality(options) {
2022
2021
  const {
@@ -2380,7 +2379,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2380
2379
  ES_LOCALE,
2381
2380
  EYE_LANDMARK_INDICES,
2382
2381
  EYE_QUALITY_THRESHOLDS,
2383
- FACE_CENTER_VERTICAL_OFFSET,
2382
+ FACE_CROP_FRAME_MARGIN,
2384
2383
  FACE_CROP_OUTPUT_SIZE,
2385
2384
  FEEDBACK_MESSAGES,
2386
2385
  FRAME_BUFFER_CONFIG,
@@ -2390,20 +2389,17 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2390
2389
  GOOD_ALIGNMENT,
2391
2390
  HIGH_ALIGNMENT,
2392
2391
  HYBRID_MODEL_CONFIGS,
2393
- IDEAL_CROP_MULTIPLIER,
2394
2392
  LANDMARK_INDEX,
2395
2393
  LANDMARK_MAX_BOUND,
2396
2394
  LANDMARK_MIN_BOUND,
2397
2395
  LOW_LIGHT_THRESHOLD,
2398
2396
  LivenessApiError,
2399
2397
  LivenessClient,
2400
- MAX_CROP_MULTIPLIER,
2401
2398
  MAX_FACE_PERCENTAGE_IN_CROP,
2402
2399
  MAX_FACE_RATIO,
2403
2400
  MAX_FACE_ROLL_DEGREES,
2404
2401
  MAX_IDEAL_FACE_RATIO,
2405
2402
  MIN_CAPTURE_ALIGNMENT,
2406
- MIN_CROP_MULTIPLIER,
2407
2403
  MIN_FACE_AREA_RATIO,
2408
2404
  MIN_FACE_BOTTOM_MARGIN,
2409
2405
  MIN_FACE_RATIO,
@@ -2425,7 +2421,6 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2425
2421
  analyzeEyeRegionBrightness,
2426
2422
  analyzeEyeRegionContrast,
2427
2423
  analyzeLighting,
2428
- calculateAdaptiveCropMultiplier,
2429
2424
  calculateBrightness,
2430
2425
  calculateFaceAlignment,
2431
2426
  calculateFaceCropRegion,
package/dist/index.mjs CHANGED
@@ -16,7 +16,8 @@ var API_PATHS = {
16
16
  hybrid50: "/api/v1/hybrid-50",
17
17
  hybrid150: "/api/v1/hybrid-150",
18
18
  jobResult: "/api/v1/result",
19
- queueStats: "/api/v1/queue/stats"
19
+ queueStats: "/api/v1/queue/stats",
20
+ sessions: "/api/v1/sessions"
20
21
  };
21
22
  var RETRY_CONFIG = {
22
23
  maxAttempts: 3,
@@ -96,7 +97,7 @@ async function sleep(ms) {
96
97
  }
97
98
 
98
99
  // package.json
99
- var version = "3.8.1";
100
+ var version = "3.9.0";
100
101
 
101
102
  // src/utils/deviceIntelligence.ts
102
103
  var IPINFO_URL = "https://ipinfo.io/json";
@@ -244,6 +245,8 @@ var LivenessClient = class _LivenessClient {
244
245
  this.enableRetry = config.enableRetry ?? true;
245
246
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
246
247
  this.diOverrides = { ...config.deviceIntelligenceOverrides };
248
+ this.consumer = config.consumer;
249
+ this.trackClientTime = config.trackClientTime ?? false;
247
250
  }
248
251
  /**
249
252
  * Merge additional device intelligence overrides into the existing set.
@@ -492,6 +495,25 @@ var LivenessClient = class _LivenessClient {
492
495
  async queueStats() {
493
496
  return this.request(API_PATHS.queueStats);
494
497
  }
498
+ /**
499
+ * Post end-to-end client duration for a completed verification session.
500
+ * Fire-and-forget — swallows all errors, no retry.
501
+ * No-op when `trackClientTime` is false (default).
502
+ */
503
+ async postClientTime(sessionId, clientTime) {
504
+ if (!this.trackClientTime) return;
505
+ try {
506
+ await this.fetchFn(`${this.baseUrl}${API_PATHS.sessions}/${sessionId}/client-time`, {
507
+ method: "PATCH",
508
+ headers: {
509
+ "Content-Type": "application/json",
510
+ [AUTH_CONFIG.apiKeyHeader]: this.apiKey
511
+ },
512
+ body: JSON.stringify({ client_time: clientTime })
513
+ });
514
+ } catch {
515
+ }
516
+ }
495
517
  // ===========================================================================
496
518
  // Fast Check Endpoints
497
519
  // ===========================================================================
@@ -512,7 +534,8 @@ var LivenessClient = class _LivenessClient {
512
534
  frames: toFrameData(frames),
513
535
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
514
536
  ...options.warnings?.length ? { warnings: options.warnings } : {},
515
- ...di ? { device_intelligence: di } : {}
537
+ ...di ? { device_intelligence: di } : {},
538
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
516
539
  };
517
540
  const { data: response, headers } = await this.requestWithRetryRaw(
518
541
  API_PATHS.fastCheck,
@@ -549,7 +572,8 @@ var LivenessClient = class _LivenessClient {
549
572
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
550
573
  ...options.warnings?.length ? { warnings: options.warnings } : {},
551
574
  ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {},
552
- ...di ? { device_intelligence: di } : {}
575
+ ...di ? { device_intelligence: di } : {},
576
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
553
577
  };
554
578
  const { data: response, headers } = await this.requestWithRetryRaw(
555
579
  API_PATHS.fastCheckCrops,
@@ -613,7 +637,8 @@ var LivenessClient = class _LivenessClient {
613
637
  frame: frameData,
614
638
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
615
639
  ...options.warnings?.length ? { warnings: options.warnings } : {},
616
- ...di ? { device_intelligence: di } : {}
640
+ ...di ? { device_intelligence: di } : {},
641
+ ...this.consumer ? { metadata: { consumer: this.consumer } } : {}
617
642
  };
618
643
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
619
644
  method: "POST",
@@ -1640,10 +1665,7 @@ var MIN_FACE_SIDE_MARGIN = 0.05;
1640
1665
  var MIN_CAPTURE_ALIGNMENT = 0.6;
1641
1666
  var HIGH_ALIGNMENT = 0.85;
1642
1667
  var GOOD_ALIGNMENT = 0.5;
1643
- var IDEAL_CROP_MULTIPLIER = 3;
1644
- var MIN_CROP_MULTIPLIER = 2.5;
1645
- var MAX_CROP_MULTIPLIER = 4;
1646
- var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1668
+ var FACE_CROP_FRAME_MARGIN = 0.2;
1647
1669
  var MIN_IDEAL_FACE_RATIO = 0.05;
1648
1670
  var MAX_IDEAL_FACE_RATIO = 0.2;
1649
1671
  var MIN_FACE_RATIO = 0.036;
@@ -1837,40 +1859,21 @@ function calculateFaceAlignment(boundingBox, frameWidth, frameHeight) {
1837
1859
  tooFar: faceRatio < MIN_FACE_RATIO
1838
1860
  };
1839
1861
  }
1840
- function calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight) {
1841
- const faceSize = Math.max(faceBox.width, faceBox.height);
1842
- const idealCropSize = faceSize * IDEAL_CROP_MULTIPLIER;
1843
- const maxCropSize = Math.min(frameWidth, frameHeight);
1844
- if (idealCropSize <= maxCropSize) {
1845
- return IDEAL_CROP_MULTIPLIER;
1846
- }
1847
- const adaptiveMultiplier = maxCropSize / faceSize;
1848
- return Math.max(MIN_CROP_MULTIPLIER, Math.min(MAX_CROP_MULTIPLIER, adaptiveMultiplier));
1849
- }
1850
1862
  function isFaceCropFullyInFrame(faceBox, frameWidth, frameHeight) {
1851
- const marginMultiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
1852
- const marginX = faceBox.width * (marginMultiplier - 1) / 2;
1853
- const marginY = faceBox.height * (marginMultiplier - 1) / 2;
1854
- const faceLeft = faceBox.originX - marginX;
1855
- const faceTop = faceBox.originY - marginY;
1856
- const faceRight = faceBox.originX + faceBox.width + marginX;
1857
- const faceBottom = faceBox.originY + faceBox.height + marginY;
1863
+ const faceLeft = faceBox.originX;
1864
+ const faceTop = faceBox.originY;
1865
+ const faceRight = faceBox.originX + faceBox.width;
1866
+ const faceBottom = faceBox.originY + faceBox.height;
1858
1867
  return faceLeft >= 0 && faceTop >= 0 && faceRight <= frameWidth && faceBottom <= frameHeight;
1859
1868
  }
1860
1869
  function calculateFaceCropRegion(faceBox, frameWidth, frameHeight) {
1861
- const cropMultiplier = calculateAdaptiveCropMultiplier(faceBox, frameWidth, frameHeight);
1862
- const expandedWidth = faceBox.width * cropMultiplier;
1863
- const expandedHeight = faceBox.height * cropMultiplier;
1864
- const cropSize = Math.max(expandedWidth, expandedHeight);
1865
- const centerX = faceBox.originX + faceBox.width / 2;
1866
- const verticalShift = faceBox.height * FACE_CENTER_VERTICAL_OFFSET;
1867
- const centerY = faceBox.originY + faceBox.height / 2 + verticalShift;
1868
- let cropX = centerX - cropSize / 2;
1869
- let cropY = centerY - cropSize / 2;
1870
- cropX = Math.max(0, Math.min(frameWidth - cropSize, cropX));
1871
- cropY = Math.max(0, Math.min(frameHeight - cropSize, cropY));
1872
- const finalSize = Math.min(cropSize, frameWidth - cropX, frameHeight - cropY);
1873
- return { x: cropX, y: cropY, size: finalSize };
1870
+ const marginX = FACE_CROP_FRAME_MARGIN * frameWidth;
1871
+ const marginY = FACE_CROP_FRAME_MARGIN * frameHeight;
1872
+ const x1 = Math.max(0, faceBox.originX - marginX);
1873
+ const y1 = Math.max(0, faceBox.originY - marginY);
1874
+ const x2 = Math.min(frameWidth, faceBox.originX + faceBox.width + marginX);
1875
+ const y2 = Math.min(frameHeight, faceBox.originY + faceBox.height + marginY);
1876
+ return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
1874
1877
  }
1875
1878
  function checkFrameQuality(options) {
1876
1879
  const {
@@ -2233,7 +2236,7 @@ export {
2233
2236
  ES_LOCALE,
2234
2237
  EYE_LANDMARK_INDICES,
2235
2238
  EYE_QUALITY_THRESHOLDS,
2236
- FACE_CENTER_VERTICAL_OFFSET,
2239
+ FACE_CROP_FRAME_MARGIN,
2237
2240
  FACE_CROP_OUTPUT_SIZE,
2238
2241
  FEEDBACK_MESSAGES,
2239
2242
  FRAME_BUFFER_CONFIG,
@@ -2243,20 +2246,17 @@ export {
2243
2246
  GOOD_ALIGNMENT,
2244
2247
  HIGH_ALIGNMENT,
2245
2248
  HYBRID_MODEL_CONFIGS,
2246
- IDEAL_CROP_MULTIPLIER,
2247
2249
  LANDMARK_INDEX,
2248
2250
  LANDMARK_MAX_BOUND,
2249
2251
  LANDMARK_MIN_BOUND,
2250
2252
  LOW_LIGHT_THRESHOLD,
2251
2253
  LivenessApiError,
2252
2254
  LivenessClient,
2253
- MAX_CROP_MULTIPLIER,
2254
2255
  MAX_FACE_PERCENTAGE_IN_CROP,
2255
2256
  MAX_FACE_RATIO,
2256
2257
  MAX_FACE_ROLL_DEGREES,
2257
2258
  MAX_IDEAL_FACE_RATIO,
2258
2259
  MIN_CAPTURE_ALIGNMENT,
2259
- MIN_CROP_MULTIPLIER,
2260
2260
  MIN_FACE_AREA_RATIO,
2261
2261
  MIN_FACE_BOTTOM_MARGIN,
2262
2262
  MIN_FACE_RATIO,
@@ -2278,7 +2278,6 @@ export {
2278
2278
  analyzeEyeRegionBrightness,
2279
2279
  analyzeEyeRegionContrast,
2280
2280
  analyzeLighting,
2281
- calculateAdaptiveCropMultiplier,
2282
2281
  calculateBrightness,
2283
2282
  calculateFaceAlignment,
2284
2283
  calculateFaceCropRegion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "3.8.1",
3
+ "version": "3.9.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",