@moveris/shared 3.13.0 → 3.16.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
@@ -97,7 +97,46 @@ interface LiveCheckRequest {
97
97
  metadata?: {
98
98
  consumer?: ConsumerContext;
99
99
  } | null;
100
+ trace_id?: string;
101
+ forensic_client?: unknown;
100
102
  }
103
+ interface V2UploadFrameData extends FrameData {
104
+ landmarks?: {
105
+ x: number;
106
+ y: number;
107
+ z: number;
108
+ }[] | null;
109
+ }
110
+ interface V2UploadRequest {
111
+ session_id: string;
112
+ model?: FastCheckModel;
113
+ source?: FrameSource;
114
+ fps?: number;
115
+ frames: V2UploadFrameData[];
116
+ frame_count?: number;
117
+ warnings?: string[];
118
+ }
119
+ interface V2UploadResponse {
120
+ session_id: string;
121
+ frames_received: number;
122
+ frames_required: number;
123
+ is_complete: boolean;
124
+ ttl_seconds: number;
125
+ stored_durable: boolean;
126
+ }
127
+ interface V2FastCheckRequest {
128
+ session_id: string;
129
+ model?: FastCheckModel;
130
+ source?: FrameSource;
131
+ fps?: number;
132
+ frame_count?: number;
133
+ warnings?: string[];
134
+ device_intelligence?: DeviceIntelligence;
135
+ metadata?: {
136
+ consumer?: ConsumerContext;
137
+ } | null;
138
+ }
139
+ type V2LiveCheckRequest = V2FastCheckRequest;
101
140
  interface FastCheckCropsRequest {
102
141
  session_id: string;
103
142
  model?: FastCheckModel;
@@ -602,6 +641,59 @@ interface CameraValidationResult {
602
641
  }
603
642
  declare const DEFAULT_CAMERA_REQUIREMENTS: CameraRequirements;
604
643
 
644
+ declare const FORENSIC_SCHEMA_VERSION = "1.0";
645
+ type ForensicSource = 'webcam' | 'media';
646
+ interface ClientStage {
647
+ stage_id: 'capture' | 'landmarks' | 'encode' | 'transport';
648
+ layer: 'sdk' | 'wire';
649
+ ok: boolean;
650
+ metrics: Record<string, unknown>;
651
+ }
652
+ interface ClientFragment {
653
+ trace_id: string;
654
+ schema_version: string;
655
+ source: ForensicSource;
656
+ origin: {
657
+ sdk_version?: string;
658
+ demo_commit?: string;
659
+ video_path?: string;
660
+ };
661
+ target: {
662
+ environment?: string;
663
+ model?: string;
664
+ endpoint?: string;
665
+ };
666
+ stages: ClientStage[];
667
+ }
668
+ interface CaptureSummary {
669
+ framesCaptured: number;
670
+ alignmentMean?: number;
671
+ blurVarMean?: number;
672
+ brightnessMean?: number;
673
+ landmarksPresentPct?: number;
674
+ validationPassPct?: number;
675
+ rollMean?: number;
676
+ yawMean?: number;
677
+ pitchMean?: number;
678
+ encoding?: string;
679
+ width?: number;
680
+ height?: number;
681
+ bytesPerFrame?: number;
682
+ framesSent?: number;
683
+ requestBytes?: number;
684
+ sessionId?: string;
685
+ }
686
+ interface FragmentContext {
687
+ sdkVersion?: string;
688
+ demoCommit?: string;
689
+ videoPath?: string;
690
+ environment?: string;
691
+ model?: string;
692
+ endpoint?: string;
693
+ }
694
+ declare function generateTraceId(): string;
695
+ declare function buildClientFragment(traceId: string, source: ForensicSource, summary: CaptureSummary, ctx?: FragmentContext): ClientFragment;
696
+
605
697
  interface LivenessClientConfig {
606
698
  baseUrl?: string;
607
699
  apiKey: string;
@@ -613,6 +705,7 @@ interface LivenessClientConfig {
613
705
  consumer?: ConsumerContext;
614
706
  trackClientTime?: boolean;
615
707
  extraMetadata?: ExtraMetadata;
708
+ traceId?: string;
616
709
  }
617
710
  declare class LivenessApiError extends Error {
618
711
  readonly code: string;
@@ -622,6 +715,9 @@ declare class LivenessApiError extends Error {
622
715
  constructor(message: string, code: string, statusCode: number, required?: number, received?: number);
623
716
  }
624
717
  declare function toFrameData(frames: CapturedFrame[]): FrameData[];
718
+ declare function toV2UploadFrameData(frames: CapturedFrame[], options?: {
719
+ withLandmarks?: boolean;
720
+ }): V2UploadFrameData[];
625
721
  declare function toHybridFrameData(frames: CapturedFrame[]): HybridFrameData[];
626
722
  declare function toLivenessResult(response: FastCheckResponse | VerifyResponse | HybridCheckResponse): LivenessResult;
627
723
  declare function toLivenessResultFromStream(response: FastCheckStreamResponse): LivenessResult;
@@ -636,6 +732,7 @@ declare class LivenessClient {
636
732
  private readonly consumer;
637
733
  private readonly trackClientTime;
638
734
  private readonly extraMetadata;
735
+ private readonly traceId;
639
736
  private diCollected;
640
737
  private diCollecting;
641
738
  private diOverrides;
@@ -673,6 +770,8 @@ declare class LivenessClient {
673
770
  frameCount?: number;
674
771
  source?: FrameSource;
675
772
  warnings?: string[];
773
+ traceId?: string;
774
+ forensic?: ClientFragment;
676
775
  }): Promise<LivenessResult>;
677
776
  fastCheckCrops(crops: CropData[], options?: {
678
777
  sessionId?: string;
@@ -735,6 +834,35 @@ declare class LivenessClient {
735
834
  }): Promise<LivenessResult>;
736
835
  getJobResult(jobId: string): Promise<JobStatusResponse>;
737
836
  waitForJobResult(jobId: string, timeout?: number): Promise<JobStatusResponse>;
837
+ v2Upload(frames: CapturedFrame[], options: {
838
+ sessionId: string;
839
+ model?: FastCheckModel;
840
+ modelVersion?: ModelVersion;
841
+ source?: FrameSource;
842
+ fps?: number;
843
+ frameCount?: number;
844
+ warnings?: string[];
845
+ withLandmarks?: boolean;
846
+ }): Promise<V2UploadResponse>;
847
+ v2FastCheck(options: {
848
+ sessionId: string;
849
+ model?: FastCheckModel;
850
+ modelVersion?: ModelVersion;
851
+ source?: FrameSource;
852
+ fps?: number;
853
+ frameCount?: number;
854
+ warnings?: string[];
855
+ }): Promise<LivenessResult>;
856
+ v2LiveCheck(options: {
857
+ sessionId: string;
858
+ model?: FastCheckModel;
859
+ modelVersion?: ModelVersion;
860
+ source?: FrameSource;
861
+ fps?: number;
862
+ frameCount?: number;
863
+ warnings?: string[];
864
+ }): Promise<LivenessResult>;
865
+ private runV2Check;
738
866
  }
739
867
 
740
868
  declare class FrameBuffer {
@@ -789,6 +917,9 @@ declare const API_PATHS: {
789
917
  readonly jobResult: "/api/v1/result";
790
918
  readonly queueStats: "/api/v1/queue/stats";
791
919
  readonly sessions: "/api/v1/sessions";
920
+ readonly v2Upload: "/api/v2/upload";
921
+ readonly v2FastCheck: "/api/v2/fast-check";
922
+ readonly v2LiveCheck: "/api/v2/live-check";
792
923
  };
793
924
  declare const RETRY_CONFIG: {
794
925
  readonly maxAttempts: 3;
@@ -1003,4 +1134,51 @@ declare function collectDeviceIntelligence(opts?: {
1003
1134
  platformVersion?: string;
1004
1135
  }): Promise<DeviceIntelligence | null>;
1005
1136
 
1006
- 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 ExtraMetadata, 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, type LiveCheckFrameData, type LiveCheckRequest, 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 };
1137
+ interface RecorderFrame {
1138
+ pixels?: string;
1139
+ landmarks?: unknown[] | null;
1140
+ }
1141
+ interface RecorderQuality {
1142
+ alignment?: number;
1143
+ blurVariance?: number;
1144
+ brightness?: number;
1145
+ landmarksValid?: boolean;
1146
+ width?: number;
1147
+ height?: number;
1148
+ encoding?: string;
1149
+ }
1150
+ declare class ForensicRecorder {
1151
+ readonly traceId: string;
1152
+ private readonly source;
1153
+ private readonly ctx;
1154
+ private framesCaptured;
1155
+ private landmarksPresent;
1156
+ private landmarksValid;
1157
+ private qualityTicks;
1158
+ private bytesTotal;
1159
+ private encoding;
1160
+ private width;
1161
+ private height;
1162
+ private readonly alignment;
1163
+ private readonly blur;
1164
+ private readonly brightness;
1165
+ private framesSent;
1166
+ private requestBytes;
1167
+ private sessionId;
1168
+ constructor(opts?: {
1169
+ source?: ForensicSource;
1170
+ traceId?: string;
1171
+ ctx?: FragmentContext;
1172
+ });
1173
+ onFrame(frame: RecorderFrame): void;
1174
+ onQuality(q: RecorderQuality): void;
1175
+ setTransport(t: {
1176
+ framesSent?: number;
1177
+ requestBytes?: number;
1178
+ sessionId?: string;
1179
+ }): void;
1180
+ summary(): CaptureSummary;
1181
+ build(): ClientFragment;
1182
+ }
1183
+
1184
+ 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 CaptureSummary, type CapturedFrame, type ClientFragment, type ClientStage, 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 ExtraMetadata, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, FACE_CROP_FRAME_MARGIN, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FORENSIC_SCHEMA_VERSION, 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, ForensicRecorder, type ForensicSource, type FragmentContext, 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, type LiveCheckFrameData, type LiveCheckRequest, 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 RecorderFrame, type RecorderQuality, type RetryOptions, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type V2FastCheckRequest, type V2LiveCheckRequest, type V2UploadFrameData, type V2UploadRequest, type V2UploadResponse, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, buildClientFragment, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, generateTraceId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, toV2UploadFrameData, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.d.ts CHANGED
@@ -97,7 +97,46 @@ interface LiveCheckRequest {
97
97
  metadata?: {
98
98
  consumer?: ConsumerContext;
99
99
  } | null;
100
+ trace_id?: string;
101
+ forensic_client?: unknown;
100
102
  }
103
+ interface V2UploadFrameData extends FrameData {
104
+ landmarks?: {
105
+ x: number;
106
+ y: number;
107
+ z: number;
108
+ }[] | null;
109
+ }
110
+ interface V2UploadRequest {
111
+ session_id: string;
112
+ model?: FastCheckModel;
113
+ source?: FrameSource;
114
+ fps?: number;
115
+ frames: V2UploadFrameData[];
116
+ frame_count?: number;
117
+ warnings?: string[];
118
+ }
119
+ interface V2UploadResponse {
120
+ session_id: string;
121
+ frames_received: number;
122
+ frames_required: number;
123
+ is_complete: boolean;
124
+ ttl_seconds: number;
125
+ stored_durable: boolean;
126
+ }
127
+ interface V2FastCheckRequest {
128
+ session_id: string;
129
+ model?: FastCheckModel;
130
+ source?: FrameSource;
131
+ fps?: number;
132
+ frame_count?: number;
133
+ warnings?: string[];
134
+ device_intelligence?: DeviceIntelligence;
135
+ metadata?: {
136
+ consumer?: ConsumerContext;
137
+ } | null;
138
+ }
139
+ type V2LiveCheckRequest = V2FastCheckRequest;
101
140
  interface FastCheckCropsRequest {
102
141
  session_id: string;
103
142
  model?: FastCheckModel;
@@ -602,6 +641,59 @@ interface CameraValidationResult {
602
641
  }
603
642
  declare const DEFAULT_CAMERA_REQUIREMENTS: CameraRequirements;
604
643
 
644
+ declare const FORENSIC_SCHEMA_VERSION = "1.0";
645
+ type ForensicSource = 'webcam' | 'media';
646
+ interface ClientStage {
647
+ stage_id: 'capture' | 'landmarks' | 'encode' | 'transport';
648
+ layer: 'sdk' | 'wire';
649
+ ok: boolean;
650
+ metrics: Record<string, unknown>;
651
+ }
652
+ interface ClientFragment {
653
+ trace_id: string;
654
+ schema_version: string;
655
+ source: ForensicSource;
656
+ origin: {
657
+ sdk_version?: string;
658
+ demo_commit?: string;
659
+ video_path?: string;
660
+ };
661
+ target: {
662
+ environment?: string;
663
+ model?: string;
664
+ endpoint?: string;
665
+ };
666
+ stages: ClientStage[];
667
+ }
668
+ interface CaptureSummary {
669
+ framesCaptured: number;
670
+ alignmentMean?: number;
671
+ blurVarMean?: number;
672
+ brightnessMean?: number;
673
+ landmarksPresentPct?: number;
674
+ validationPassPct?: number;
675
+ rollMean?: number;
676
+ yawMean?: number;
677
+ pitchMean?: number;
678
+ encoding?: string;
679
+ width?: number;
680
+ height?: number;
681
+ bytesPerFrame?: number;
682
+ framesSent?: number;
683
+ requestBytes?: number;
684
+ sessionId?: string;
685
+ }
686
+ interface FragmentContext {
687
+ sdkVersion?: string;
688
+ demoCommit?: string;
689
+ videoPath?: string;
690
+ environment?: string;
691
+ model?: string;
692
+ endpoint?: string;
693
+ }
694
+ declare function generateTraceId(): string;
695
+ declare function buildClientFragment(traceId: string, source: ForensicSource, summary: CaptureSummary, ctx?: FragmentContext): ClientFragment;
696
+
605
697
  interface LivenessClientConfig {
606
698
  baseUrl?: string;
607
699
  apiKey: string;
@@ -613,6 +705,7 @@ interface LivenessClientConfig {
613
705
  consumer?: ConsumerContext;
614
706
  trackClientTime?: boolean;
615
707
  extraMetadata?: ExtraMetadata;
708
+ traceId?: string;
616
709
  }
617
710
  declare class LivenessApiError extends Error {
618
711
  readonly code: string;
@@ -622,6 +715,9 @@ declare class LivenessApiError extends Error {
622
715
  constructor(message: string, code: string, statusCode: number, required?: number, received?: number);
623
716
  }
624
717
  declare function toFrameData(frames: CapturedFrame[]): FrameData[];
718
+ declare function toV2UploadFrameData(frames: CapturedFrame[], options?: {
719
+ withLandmarks?: boolean;
720
+ }): V2UploadFrameData[];
625
721
  declare function toHybridFrameData(frames: CapturedFrame[]): HybridFrameData[];
626
722
  declare function toLivenessResult(response: FastCheckResponse | VerifyResponse | HybridCheckResponse): LivenessResult;
627
723
  declare function toLivenessResultFromStream(response: FastCheckStreamResponse): LivenessResult;
@@ -636,6 +732,7 @@ declare class LivenessClient {
636
732
  private readonly consumer;
637
733
  private readonly trackClientTime;
638
734
  private readonly extraMetadata;
735
+ private readonly traceId;
639
736
  private diCollected;
640
737
  private diCollecting;
641
738
  private diOverrides;
@@ -673,6 +770,8 @@ declare class LivenessClient {
673
770
  frameCount?: number;
674
771
  source?: FrameSource;
675
772
  warnings?: string[];
773
+ traceId?: string;
774
+ forensic?: ClientFragment;
676
775
  }): Promise<LivenessResult>;
677
776
  fastCheckCrops(crops: CropData[], options?: {
678
777
  sessionId?: string;
@@ -735,6 +834,35 @@ declare class LivenessClient {
735
834
  }): Promise<LivenessResult>;
736
835
  getJobResult(jobId: string): Promise<JobStatusResponse>;
737
836
  waitForJobResult(jobId: string, timeout?: number): Promise<JobStatusResponse>;
837
+ v2Upload(frames: CapturedFrame[], options: {
838
+ sessionId: string;
839
+ model?: FastCheckModel;
840
+ modelVersion?: ModelVersion;
841
+ source?: FrameSource;
842
+ fps?: number;
843
+ frameCount?: number;
844
+ warnings?: string[];
845
+ withLandmarks?: boolean;
846
+ }): Promise<V2UploadResponse>;
847
+ v2FastCheck(options: {
848
+ sessionId: string;
849
+ model?: FastCheckModel;
850
+ modelVersion?: ModelVersion;
851
+ source?: FrameSource;
852
+ fps?: number;
853
+ frameCount?: number;
854
+ warnings?: string[];
855
+ }): Promise<LivenessResult>;
856
+ v2LiveCheck(options: {
857
+ sessionId: string;
858
+ model?: FastCheckModel;
859
+ modelVersion?: ModelVersion;
860
+ source?: FrameSource;
861
+ fps?: number;
862
+ frameCount?: number;
863
+ warnings?: string[];
864
+ }): Promise<LivenessResult>;
865
+ private runV2Check;
738
866
  }
739
867
 
740
868
  declare class FrameBuffer {
@@ -789,6 +917,9 @@ declare const API_PATHS: {
789
917
  readonly jobResult: "/api/v1/result";
790
918
  readonly queueStats: "/api/v1/queue/stats";
791
919
  readonly sessions: "/api/v1/sessions";
920
+ readonly v2Upload: "/api/v2/upload";
921
+ readonly v2FastCheck: "/api/v2/fast-check";
922
+ readonly v2LiveCheck: "/api/v2/live-check";
792
923
  };
793
924
  declare const RETRY_CONFIG: {
794
925
  readonly maxAttempts: 3;
@@ -1003,4 +1134,51 @@ declare function collectDeviceIntelligence(opts?: {
1003
1134
  platformVersion?: string;
1004
1135
  }): Promise<DeviceIntelligence | null>;
1005
1136
 
1006
- 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 ExtraMetadata, 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, type LiveCheckFrameData, type LiveCheckRequest, 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 };
1137
+ interface RecorderFrame {
1138
+ pixels?: string;
1139
+ landmarks?: unknown[] | null;
1140
+ }
1141
+ interface RecorderQuality {
1142
+ alignment?: number;
1143
+ blurVariance?: number;
1144
+ brightness?: number;
1145
+ landmarksValid?: boolean;
1146
+ width?: number;
1147
+ height?: number;
1148
+ encoding?: string;
1149
+ }
1150
+ declare class ForensicRecorder {
1151
+ readonly traceId: string;
1152
+ private readonly source;
1153
+ private readonly ctx;
1154
+ private framesCaptured;
1155
+ private landmarksPresent;
1156
+ private landmarksValid;
1157
+ private qualityTicks;
1158
+ private bytesTotal;
1159
+ private encoding;
1160
+ private width;
1161
+ private height;
1162
+ private readonly alignment;
1163
+ private readonly blur;
1164
+ private readonly brightness;
1165
+ private framesSent;
1166
+ private requestBytes;
1167
+ private sessionId;
1168
+ constructor(opts?: {
1169
+ source?: ForensicSource;
1170
+ traceId?: string;
1171
+ ctx?: FragmentContext;
1172
+ });
1173
+ onFrame(frame: RecorderFrame): void;
1174
+ onQuality(q: RecorderQuality): void;
1175
+ setTransport(t: {
1176
+ framesSent?: number;
1177
+ requestBytes?: number;
1178
+ sessionId?: string;
1179
+ }): void;
1180
+ summary(): CaptureSummary;
1181
+ build(): ClientFragment;
1182
+ }
1183
+
1184
+ 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 CaptureSummary, type CapturedFrame, type ClientFragment, type ClientStage, 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 ExtraMetadata, type EyeQualityThresholds, type EyeRegionBounds, type EyeRegionQuality, type EyeRegionsBounds, FACE_CROP_FRAME_MARGIN, FACE_CROP_OUTPUT_SIZE, FEEDBACK_MESSAGES, FORENSIC_SCHEMA_VERSION, 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, ForensicRecorder, type ForensicSource, type FragmentContext, 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, type LiveCheckFrameData, type LiveCheckRequest, 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 RecorderFrame, type RecorderQuality, type RetryOptions, SHARED_SDK_PLATFORM, type StabilizationProgress, type StabilizationResult, type StabilizerConfig, type StatusMessageKey, type StreamingStatus, TARGET_FACE_PERCENTAGE_IN_CROP, type V2FastCheckRequest, type V2LiveCheckRequest, type V2UploadFrameData, type V2UploadRequest, type V2UploadResponse, VALID_FRAME_COUNTS, type Verdict, type VerifyRequest, type VerifyResponse, type VideoFrameMetadata, analyzeBlur, analyzeDynamicRange, analyzeEyeRegionBrightness, analyzeEyeRegionContrast, analyzeLighting, buildClientFragment, calculateBrightness, calculateFaceAlignment, calculateFaceCropRegion, canCaptureFrame, checkEyeRegionQuality, checkFrameQuality, collectDeviceIntelligence, decodeBase64, detectCameraAngle, detectFaceRoll, detectFaceRollFromMatrix, detectSpecularHighlights, encodeBase64, generateSessionId, generateTraceId, getActiveModels, getApiErrorMessage, getCaptureQualityFeedback, getEyeRegionBounds, getFeedbackMessage, getMinFramesForModel, getOvalGuideState, getStatusMessage, hasEnoughFrames, isDeprecatedModel, isFaceCropFullyInFrame, isFaceFullyVisible, isFaceInOval, isRetryableError, linearRgbToLabL, retryWithBackoff, rgbaToGrayscale, sleep, srgbToLinear, toFrameData, toHybridFrameData, toLivenessResult, toLivenessResultFromStream, toV2UploadFrameData, validateApiKey, validateFaceLandmarks, validateFrameCount, validateFrameData, validateFrameIndex, validateTimestamp, validateUUID, validateUrl };
package/dist/index.js CHANGED
@@ -53,8 +53,10 @@ __export(index_exports, {
53
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
+ FORENSIC_SCHEMA_VERSION: () => FORENSIC_SCHEMA_VERSION,
56
57
  FRAME_BUFFER_CONFIG: () => FRAME_BUFFER_CONFIG,
57
58
  FRAME_CONFIG: () => FRAME_CONFIG,
59
+ ForensicRecorder: () => ForensicRecorder,
58
60
  FrameBuffer: () => FrameBuffer,
59
61
  FrameQueue: () => FrameQueue,
60
62
  GOOD_ALIGNMENT: () => GOOD_ALIGNMENT,
@@ -92,6 +94,7 @@ __export(index_exports, {
92
94
  analyzeEyeRegionBrightness: () => analyzeEyeRegionBrightness,
93
95
  analyzeEyeRegionContrast: () => analyzeEyeRegionContrast,
94
96
  analyzeLighting: () => analyzeLighting,
97
+ buildClientFragment: () => buildClientFragment,
95
98
  calculateBrightness: () => calculateBrightness,
96
99
  calculateFaceAlignment: () => calculateFaceAlignment,
97
100
  calculateFaceCropRegion: () => calculateFaceCropRegion,
@@ -106,6 +109,7 @@ __export(index_exports, {
106
109
  detectSpecularHighlights: () => detectSpecularHighlights,
107
110
  encodeBase64: () => encodeBase64,
108
111
  generateSessionId: () => generateSessionId,
112
+ generateTraceId: () => generateTraceId,
109
113
  getActiveModels: () => getActiveModels,
110
114
  getApiErrorMessage: () => getApiErrorMessage,
111
115
  getCaptureQualityFeedback: () => getCaptureQualityFeedback,
@@ -129,6 +133,7 @@ __export(index_exports, {
129
133
  toHybridFrameData: () => toHybridFrameData,
130
134
  toLivenessResult: () => toLivenessResult,
131
135
  toLivenessResultFromStream: () => toLivenessResultFromStream,
136
+ toV2UploadFrameData: () => toV2UploadFrameData,
132
137
  validateApiKey: () => validateApiKey,
133
138
  validateFaceLandmarks: () => validateFaceLandmarks,
134
139
  validateFrameCount: () => validateFrameCount,
@@ -161,7 +166,12 @@ var API_PATHS = {
161
166
  hybrid150: "/api/v1/hybrid-150",
162
167
  jobResult: "/api/v1/result",
163
168
  queueStats: "/api/v1/queue/stats",
164
- sessions: "/api/v1/sessions"
169
+ sessions: "/api/v1/sessions",
170
+ // v2 upload-first pipeline (MOV-1936). Separate from v1; frames are uploaded
171
+ // first and resolved server-side by session_id at check time.
172
+ v2Upload: "/api/v2/upload",
173
+ v2FastCheck: "/api/v2/fast-check",
174
+ v2LiveCheck: "/api/v2/live-check"
165
175
  };
166
176
  var RETRY_CONFIG = {
167
177
  maxAttempts: 3,
@@ -241,7 +251,7 @@ async function sleep(ms) {
241
251
  }
242
252
 
243
253
  // package.json
244
- var version = "3.13.0";
254
+ var version = "3.16.0";
245
255
 
246
256
  // src/utils/deviceIntelligence.ts
247
257
  var IPINFO_URL = "https://ipinfo.io/json";
@@ -289,6 +299,85 @@ async function collectDeviceIntelligence(opts) {
289
299
  }
290
300
  }
291
301
 
302
+ // src/forensic/clientFragment.ts
303
+ var FORENSIC_SCHEMA_VERSION = "1.0";
304
+ function defined(obj) {
305
+ const out = {};
306
+ for (const [k, v] of Object.entries(obj)) {
307
+ if (v !== void 0 && v !== null) out[k] = v;
308
+ }
309
+ return out;
310
+ }
311
+ function generateTraceId() {
312
+ const c = globalThis.crypto;
313
+ if (c?.randomUUID) return c.randomUUID();
314
+ return "trace-" + Math.abs(hashString(String(Date.now()) + ":" + performanceNow())).toString(36);
315
+ }
316
+ function performanceNow() {
317
+ const p = globalThis.performance;
318
+ return p?.now ? p.now() : 0;
319
+ }
320
+ function hashString(s) {
321
+ let h = 0;
322
+ for (let i = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0;
323
+ return h;
324
+ }
325
+ function buildClientFragment(traceId, source, summary, ctx = {}) {
326
+ const stages = [];
327
+ const capture = defined({
328
+ frames_captured: summary.framesCaptured,
329
+ alignment_mean: summary.alignmentMean,
330
+ blur_var_mean: summary.blurVarMean,
331
+ brightness_mean: summary.brightnessMean
332
+ });
333
+ if (Object.keys(capture).length) {
334
+ stages.push({ stage_id: "capture", layer: "sdk", ok: true, metrics: capture });
335
+ }
336
+ const landmarks = defined({
337
+ landmarks_present_pct: summary.landmarksPresentPct,
338
+ validation_pass_pct: summary.validationPassPct,
339
+ roll_mean: summary.rollMean,
340
+ yaw_mean: summary.yawMean,
341
+ pitch_mean: summary.pitchMean
342
+ });
343
+ if (Object.keys(landmarks).length) {
344
+ stages.push({ stage_id: "landmarks", layer: "sdk", ok: true, metrics: landmarks });
345
+ }
346
+ const encode = defined({
347
+ encoding: summary.encoding,
348
+ w: summary.width,
349
+ h: summary.height,
350
+ bytes_per_frame: summary.bytesPerFrame
351
+ });
352
+ if (Object.keys(encode).length) {
353
+ stages.push({ stage_id: "encode", layer: "sdk", ok: true, metrics: encode });
354
+ }
355
+ const transport = defined({
356
+ frames_sent: summary.framesSent,
357
+ request_bytes: summary.requestBytes,
358
+ session_id: summary.sessionId
359
+ });
360
+ if (Object.keys(transport).length) {
361
+ stages.push({ stage_id: "transport", layer: "wire", ok: true, metrics: transport });
362
+ }
363
+ return {
364
+ trace_id: traceId,
365
+ schema_version: FORENSIC_SCHEMA_VERSION,
366
+ source,
367
+ origin: defined({
368
+ sdk_version: ctx.sdkVersion,
369
+ demo_commit: ctx.demoCommit,
370
+ video_path: ctx.videoPath
371
+ }),
372
+ target: defined({
373
+ environment: ctx.environment,
374
+ model: ctx.model,
375
+ endpoint: ctx.endpoint
376
+ }),
377
+ stages
378
+ };
379
+ }
380
+
292
381
  // src/client/LivenessClient.ts
293
382
  var LivenessApiError = class extends Error {
294
383
  constructor(message, code, statusCode, required, received) {
@@ -315,6 +404,16 @@ function toFrameData(frames) {
315
404
  pixels: frame.pixels
316
405
  }));
317
406
  }
407
+ function toV2UploadFrameData(frames, options = {}) {
408
+ return frames.map((frame) => ({
409
+ index: frame.index,
410
+ timestamp_ms: frame.timestampMs,
411
+ pixels: frame.pixels,
412
+ ...options.withLandmarks ? {
413
+ landmarks: frame.landmarks ? frame.landmarks.map((lm) => ({ x: lm.x, y: lm.y, z: lm.z })) : null
414
+ } : {}
415
+ }));
416
+ }
318
417
  function toHybridFrameData(frames) {
319
418
  return frames.map((frame) => ({
320
419
  timestamp_ms: frame.timestampMs,
@@ -403,6 +502,7 @@ var LivenessClient = class _LivenessClient {
403
502
  this.consumer = config.consumer;
404
503
  this.trackClientTime = config.trackClientTime ?? false;
405
504
  this.extraMetadata = config.extraMetadata;
505
+ this.traceId = config.traceId;
406
506
  }
407
507
  /**
408
508
  * Merge additional device intelligence overrides into the existing set.
@@ -732,11 +832,14 @@ var LivenessClient = class _LivenessClient {
732
832
  async liveCheck(frames, options = {}) {
733
833
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
734
834
  const di = await this.getDeviceIntelligence();
835
+ const traceId = options.forensic?.trace_id ?? options.traceId ?? this.traceId ?? generateTraceId();
735
836
  const request = {
736
837
  session_id: options.sessionId ?? generateSessionId(),
737
838
  model: options.model ?? "10",
738
839
  source: options.source ?? "live",
739
840
  frames: toLiveCheckFrameData(frames),
841
+ trace_id: traceId,
842
+ ...options.forensic ? { forensic_client: options.forensic } : {},
740
843
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
741
844
  ...options.warnings?.length ? { warnings: options.warnings } : {},
742
845
  ...di ? { device_intelligence: di } : {},
@@ -747,7 +850,7 @@ var LivenessClient = class _LivenessClient {
747
850
  {
748
851
  method: "POST",
749
852
  body: JSON.stringify(request),
750
- headers: this.buildModelVersionHeaders(effectiveVersion)
853
+ headers: { ...this.buildModelVersionHeaders(effectiveVersion), "X-Trace-Id": traceId }
751
854
  }
752
855
  );
753
856
  const result = toLivenessResult(response);
@@ -1057,6 +1160,103 @@ var LivenessClient = class _LivenessClient {
1057
1160
  `${API_PATHS.jobResult}/${jobId}/wait?timeout=${timeout}`
1058
1161
  );
1059
1162
  }
1163
+ // ===========================================================================
1164
+ // v2 Pipeline (MOV-1936): upload-first
1165
+ //
1166
+ // Two-phase flow, fully separate from the v1 endpoints above:
1167
+ // 1. v2Upload() — buffer frames server-side (call once or repeatedly).
1168
+ // 2. v2FastCheck() / v2LiveCheck() — run inference; NO frames in the body,
1169
+ // the server resolves them by session_id from the prior upload(s).
1170
+ // The check responses reuse the v1 FastCheckResponse shape, so the existing
1171
+ // `toLivenessResult` + deprecation-header handling applies unchanged.
1172
+ // ===========================================================================
1173
+ /**
1174
+ * Buffer a batch of frames for a session (POST /api/v2/upload).
1175
+ *
1176
+ * Inference is NOT run here — this only stores frames keyed by `session_id`.
1177
+ * May be called multiple times for the same session (e.g. chunked during
1178
+ * capture); the server accumulates and reports cumulative progress.
1179
+ *
1180
+ * Set `withLandmarks` for the live-check path so MediaPipe landmarks travel
1181
+ * with each frame; leave it off for the fast-check path.
1182
+ *
1183
+ * @param frames - Captured frames to buffer
1184
+ * @param options - Session, model, source and upload flags
1185
+ * @returns Upload progress (frames_received / frames_required / is_complete)
1186
+ */
1187
+ async v2Upload(frames, options) {
1188
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
1189
+ const request = {
1190
+ session_id: options.sessionId,
1191
+ model: options.model ?? "10",
1192
+ source: options.source ?? "live",
1193
+ frames: toV2UploadFrameData(frames, { withLandmarks: options.withLandmarks }),
1194
+ ...options.fps != null ? { fps: options.fps } : {},
1195
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
1196
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
1197
+ };
1198
+ return this.requestWithRetry(API_PATHS.v2Upload, {
1199
+ method: "POST",
1200
+ body: JSON.stringify(request),
1201
+ headers: this.buildModelVersionHeaders(effectiveVersion)
1202
+ });
1203
+ }
1204
+ /**
1205
+ * Run a fast-check against frames previously uploaded via {@link v2Upload}
1206
+ * (POST /api/v2/fast-check). The request carries NO frames — the server
1207
+ * resolves them by `session_id`.
1208
+ *
1209
+ * @param options - Session and request context
1210
+ * @returns Liveness result
1211
+ */
1212
+ async v2FastCheck(options) {
1213
+ return this.runV2Check(API_PATHS.v2FastCheck, options);
1214
+ }
1215
+ /**
1216
+ * Run a live-check against frames previously uploaded via {@link v2Upload}
1217
+ * with `withLandmarks: true` (POST /api/v2/live-check). The request carries
1218
+ * NO frames. The server requires a V3 model and surfaces the V3 `diagnostics`
1219
+ * block on the result.
1220
+ *
1221
+ * @param options - Session and request context
1222
+ * @returns Liveness result (includes `diagnostics`)
1223
+ */
1224
+ async v2LiveCheck(options) {
1225
+ return this.runV2Check(API_PATHS.v2LiveCheck, options);
1226
+ }
1227
+ /**
1228
+ * Shared body for the v2 fast-check / live-check requests (internal).
1229
+ * Both endpoints take the identical no-frames payload and response shape;
1230
+ * only the path differs.
1231
+ */
1232
+ async runV2Check(path, options) {
1233
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
1234
+ const di = await this.getDeviceIntelligence();
1235
+ const metadata = this.buildMetadata();
1236
+ const request = {
1237
+ session_id: options.sessionId,
1238
+ model: options.model ?? "10",
1239
+ source: options.source ?? "live",
1240
+ ...options.fps != null ? { fps: options.fps } : {},
1241
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
1242
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
1243
+ ...di ? { device_intelligence: di } : {},
1244
+ ...metadata ? { metadata } : {}
1245
+ };
1246
+ const { data: response, headers } = await this.requestWithRetryRaw(path, {
1247
+ method: "POST",
1248
+ body: JSON.stringify(request),
1249
+ headers: this.buildModelVersionHeaders(effectiveVersion)
1250
+ });
1251
+ const result = toLivenessResult(response);
1252
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
1253
+ if (result.deprecation?.deprecated) {
1254
+ console.warn(
1255
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
1256
+ );
1257
+ }
1258
+ return result;
1259
+ }
1060
1260
  };
1061
1261
 
1062
1262
  // src/buffer/FrameBuffer.ts
@@ -2461,6 +2661,85 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2461
2661
  message: null
2462
2662
  };
2463
2663
  }
2664
+
2665
+ // src/forensic/recorder.ts
2666
+ var RunningMean = class {
2667
+ constructor() {
2668
+ this.sum = 0;
2669
+ this.n = 0;
2670
+ }
2671
+ add(v) {
2672
+ if (typeof v === "number" && !Number.isNaN(v)) {
2673
+ this.sum += v;
2674
+ this.n += 1;
2675
+ }
2676
+ }
2677
+ get value() {
2678
+ return this.n ? this.sum / this.n : void 0;
2679
+ }
2680
+ };
2681
+ var ForensicRecorder = class {
2682
+ constructor(opts = {}) {
2683
+ this.framesCaptured = 0;
2684
+ this.landmarksPresent = 0;
2685
+ this.landmarksValid = 0;
2686
+ this.qualityTicks = 0;
2687
+ this.bytesTotal = 0;
2688
+ this.alignment = new RunningMean();
2689
+ this.blur = new RunningMean();
2690
+ this.brightness = new RunningMean();
2691
+ this.source = opts.source ?? "webcam";
2692
+ this.traceId = opts.traceId ?? generateTraceId();
2693
+ this.ctx = opts.ctx ?? {};
2694
+ }
2695
+ /** Record one captured frame (counts, landmark presence, encoded size). */
2696
+ onFrame(frame) {
2697
+ this.framesCaptured += 1;
2698
+ if (frame.landmarks && frame.landmarks.length) this.landmarksPresent += 1;
2699
+ if (typeof frame.pixels === "string") this.bytesTotal += frame.pixels.length;
2700
+ }
2701
+ /** Record one quality update (alignment/blur/brightness/landmark validity). */
2702
+ onQuality(q) {
2703
+ this.qualityTicks += 1;
2704
+ this.alignment.add(q.alignment);
2705
+ this.blur.add(q.blurVariance);
2706
+ this.brightness.add(q.brightness);
2707
+ if (q.landmarksValid) this.landmarksValid += 1;
2708
+ if (q.encoding) this.encoding = q.encoding;
2709
+ if (q.width) this.width = q.width;
2710
+ if (q.height) this.height = q.height;
2711
+ }
2712
+ /** Record the transport leg once the request is built/sent. */
2713
+ setTransport(t) {
2714
+ this.framesSent = t.framesSent;
2715
+ this.requestBytes = t.requestBytes;
2716
+ this.sessionId = t.sessionId;
2717
+ }
2718
+ /** Summarise the session into the CaptureSummary the fragment builder expects. */
2719
+ summary() {
2720
+ return {
2721
+ framesCaptured: this.framesCaptured,
2722
+ alignmentMean: this.alignment.value,
2723
+ blurVarMean: this.blur.value,
2724
+ brightnessMean: this.brightness.value,
2725
+ landmarksPresentPct: this.framesCaptured ? this.landmarksPresent / this.framesCaptured : void 0,
2726
+ // validity is a fraction of QUALITY ticks, not frames — quality updates fire
2727
+ // more often than frames, so dividing by frames could exceed 1.0.
2728
+ validationPassPct: this.qualityTicks ? this.landmarksValid / this.qualityTicks : void 0,
2729
+ encoding: this.encoding,
2730
+ width: this.width,
2731
+ height: this.height,
2732
+ bytesPerFrame: this.framesCaptured && this.bytesTotal ? Math.round(this.bytesTotal / this.framesCaptured) : void 0,
2733
+ framesSent: this.framesSent ?? (this.framesCaptured || void 0),
2734
+ requestBytes: this.requestBytes,
2735
+ sessionId: this.sessionId
2736
+ };
2737
+ }
2738
+ /** Build the client fragment for this session. */
2739
+ build() {
2740
+ return buildClientFragment(this.traceId, this.source, this.summary(), this.ctx);
2741
+ }
2742
+ };
2464
2743
  // Annotate the CommonJS export names for ESM import in node:
2465
2744
  0 && (module.exports = {
2466
2745
  ALIGNMENT_THRESHOLD_CAPTURE,
@@ -2496,8 +2775,10 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2496
2775
  FACE_CROP_FRAME_MARGIN,
2497
2776
  FACE_CROP_OUTPUT_SIZE,
2498
2777
  FEEDBACK_MESSAGES,
2778
+ FORENSIC_SCHEMA_VERSION,
2499
2779
  FRAME_BUFFER_CONFIG,
2500
2780
  FRAME_CONFIG,
2781
+ ForensicRecorder,
2501
2782
  FrameBuffer,
2502
2783
  FrameQueue,
2503
2784
  GOOD_ALIGNMENT,
@@ -2535,6 +2816,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2535
2816
  analyzeEyeRegionBrightness,
2536
2817
  analyzeEyeRegionContrast,
2537
2818
  analyzeLighting,
2819
+ buildClientFragment,
2538
2820
  calculateBrightness,
2539
2821
  calculateFaceAlignment,
2540
2822
  calculateFaceCropRegion,
@@ -2549,6 +2831,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2549
2831
  detectSpecularHighlights,
2550
2832
  encodeBase64,
2551
2833
  generateSessionId,
2834
+ generateTraceId,
2552
2835
  getActiveModels,
2553
2836
  getApiErrorMessage,
2554
2837
  getCaptureQualityFeedback,
@@ -2572,6 +2855,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2572
2855
  toHybridFrameData,
2573
2856
  toLivenessResult,
2574
2857
  toLivenessResultFromStream,
2858
+ toV2UploadFrameData,
2575
2859
  validateApiKey,
2576
2860
  validateFaceLandmarks,
2577
2861
  validateFrameCount,
package/dist/index.mjs CHANGED
@@ -19,7 +19,12 @@ var API_PATHS = {
19
19
  hybrid150: "/api/v1/hybrid-150",
20
20
  jobResult: "/api/v1/result",
21
21
  queueStats: "/api/v1/queue/stats",
22
- sessions: "/api/v1/sessions"
22
+ sessions: "/api/v1/sessions",
23
+ // v2 upload-first pipeline (MOV-1936). Separate from v1; frames are uploaded
24
+ // first and resolved server-side by session_id at check time.
25
+ v2Upload: "/api/v2/upload",
26
+ v2FastCheck: "/api/v2/fast-check",
27
+ v2LiveCheck: "/api/v2/live-check"
23
28
  };
24
29
  var RETRY_CONFIG = {
25
30
  maxAttempts: 3,
@@ -99,7 +104,7 @@ async function sleep(ms) {
99
104
  }
100
105
 
101
106
  // package.json
102
- var version = "3.13.0";
107
+ var version = "3.16.0";
103
108
 
104
109
  // src/utils/deviceIntelligence.ts
105
110
  var IPINFO_URL = "https://ipinfo.io/json";
@@ -147,6 +152,85 @@ async function collectDeviceIntelligence(opts) {
147
152
  }
148
153
  }
149
154
 
155
+ // src/forensic/clientFragment.ts
156
+ var FORENSIC_SCHEMA_VERSION = "1.0";
157
+ function defined(obj) {
158
+ const out = {};
159
+ for (const [k, v] of Object.entries(obj)) {
160
+ if (v !== void 0 && v !== null) out[k] = v;
161
+ }
162
+ return out;
163
+ }
164
+ function generateTraceId() {
165
+ const c = globalThis.crypto;
166
+ if (c?.randomUUID) return c.randomUUID();
167
+ return "trace-" + Math.abs(hashString(String(Date.now()) + ":" + performanceNow())).toString(36);
168
+ }
169
+ function performanceNow() {
170
+ const p = globalThis.performance;
171
+ return p?.now ? p.now() : 0;
172
+ }
173
+ function hashString(s) {
174
+ let h = 0;
175
+ for (let i = 0; i < s.length; i++) h = Math.imul(31, h) + s.charCodeAt(i) | 0;
176
+ return h;
177
+ }
178
+ function buildClientFragment(traceId, source, summary, ctx = {}) {
179
+ const stages = [];
180
+ const capture = defined({
181
+ frames_captured: summary.framesCaptured,
182
+ alignment_mean: summary.alignmentMean,
183
+ blur_var_mean: summary.blurVarMean,
184
+ brightness_mean: summary.brightnessMean
185
+ });
186
+ if (Object.keys(capture).length) {
187
+ stages.push({ stage_id: "capture", layer: "sdk", ok: true, metrics: capture });
188
+ }
189
+ const landmarks = defined({
190
+ landmarks_present_pct: summary.landmarksPresentPct,
191
+ validation_pass_pct: summary.validationPassPct,
192
+ roll_mean: summary.rollMean,
193
+ yaw_mean: summary.yawMean,
194
+ pitch_mean: summary.pitchMean
195
+ });
196
+ if (Object.keys(landmarks).length) {
197
+ stages.push({ stage_id: "landmarks", layer: "sdk", ok: true, metrics: landmarks });
198
+ }
199
+ const encode = defined({
200
+ encoding: summary.encoding,
201
+ w: summary.width,
202
+ h: summary.height,
203
+ bytes_per_frame: summary.bytesPerFrame
204
+ });
205
+ if (Object.keys(encode).length) {
206
+ stages.push({ stage_id: "encode", layer: "sdk", ok: true, metrics: encode });
207
+ }
208
+ const transport = defined({
209
+ frames_sent: summary.framesSent,
210
+ request_bytes: summary.requestBytes,
211
+ session_id: summary.sessionId
212
+ });
213
+ if (Object.keys(transport).length) {
214
+ stages.push({ stage_id: "transport", layer: "wire", ok: true, metrics: transport });
215
+ }
216
+ return {
217
+ trace_id: traceId,
218
+ schema_version: FORENSIC_SCHEMA_VERSION,
219
+ source,
220
+ origin: defined({
221
+ sdk_version: ctx.sdkVersion,
222
+ demo_commit: ctx.demoCommit,
223
+ video_path: ctx.videoPath
224
+ }),
225
+ target: defined({
226
+ environment: ctx.environment,
227
+ model: ctx.model,
228
+ endpoint: ctx.endpoint
229
+ }),
230
+ stages
231
+ };
232
+ }
233
+
150
234
  // src/client/LivenessClient.ts
151
235
  var LivenessApiError = class extends Error {
152
236
  constructor(message, code, statusCode, required, received) {
@@ -173,6 +257,16 @@ function toFrameData(frames) {
173
257
  pixels: frame.pixels
174
258
  }));
175
259
  }
260
+ function toV2UploadFrameData(frames, options = {}) {
261
+ return frames.map((frame) => ({
262
+ index: frame.index,
263
+ timestamp_ms: frame.timestampMs,
264
+ pixels: frame.pixels,
265
+ ...options.withLandmarks ? {
266
+ landmarks: frame.landmarks ? frame.landmarks.map((lm) => ({ x: lm.x, y: lm.y, z: lm.z })) : null
267
+ } : {}
268
+ }));
269
+ }
176
270
  function toHybridFrameData(frames) {
177
271
  return frames.map((frame) => ({
178
272
  timestamp_ms: frame.timestampMs,
@@ -261,6 +355,7 @@ var LivenessClient = class _LivenessClient {
261
355
  this.consumer = config.consumer;
262
356
  this.trackClientTime = config.trackClientTime ?? false;
263
357
  this.extraMetadata = config.extraMetadata;
358
+ this.traceId = config.traceId;
264
359
  }
265
360
  /**
266
361
  * Merge additional device intelligence overrides into the existing set.
@@ -590,11 +685,14 @@ var LivenessClient = class _LivenessClient {
590
685
  async liveCheck(frames, options = {}) {
591
686
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
592
687
  const di = await this.getDeviceIntelligence();
688
+ const traceId = options.forensic?.trace_id ?? options.traceId ?? this.traceId ?? generateTraceId();
593
689
  const request = {
594
690
  session_id: options.sessionId ?? generateSessionId(),
595
691
  model: options.model ?? "10",
596
692
  source: options.source ?? "live",
597
693
  frames: toLiveCheckFrameData(frames),
694
+ trace_id: traceId,
695
+ ...options.forensic ? { forensic_client: options.forensic } : {},
598
696
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
599
697
  ...options.warnings?.length ? { warnings: options.warnings } : {},
600
698
  ...di ? { device_intelligence: di } : {},
@@ -605,7 +703,7 @@ var LivenessClient = class _LivenessClient {
605
703
  {
606
704
  method: "POST",
607
705
  body: JSON.stringify(request),
608
- headers: this.buildModelVersionHeaders(effectiveVersion)
706
+ headers: { ...this.buildModelVersionHeaders(effectiveVersion), "X-Trace-Id": traceId }
609
707
  }
610
708
  );
611
709
  const result = toLivenessResult(response);
@@ -915,6 +1013,103 @@ var LivenessClient = class _LivenessClient {
915
1013
  `${API_PATHS.jobResult}/${jobId}/wait?timeout=${timeout}`
916
1014
  );
917
1015
  }
1016
+ // ===========================================================================
1017
+ // v2 Pipeline (MOV-1936): upload-first
1018
+ //
1019
+ // Two-phase flow, fully separate from the v1 endpoints above:
1020
+ // 1. v2Upload() — buffer frames server-side (call once or repeatedly).
1021
+ // 2. v2FastCheck() / v2LiveCheck() — run inference; NO frames in the body,
1022
+ // the server resolves them by session_id from the prior upload(s).
1023
+ // The check responses reuse the v1 FastCheckResponse shape, so the existing
1024
+ // `toLivenessResult` + deprecation-header handling applies unchanged.
1025
+ // ===========================================================================
1026
+ /**
1027
+ * Buffer a batch of frames for a session (POST /api/v2/upload).
1028
+ *
1029
+ * Inference is NOT run here — this only stores frames keyed by `session_id`.
1030
+ * May be called multiple times for the same session (e.g. chunked during
1031
+ * capture); the server accumulates and reports cumulative progress.
1032
+ *
1033
+ * Set `withLandmarks` for the live-check path so MediaPipe landmarks travel
1034
+ * with each frame; leave it off for the fast-check path.
1035
+ *
1036
+ * @param frames - Captured frames to buffer
1037
+ * @param options - Session, model, source and upload flags
1038
+ * @returns Upload progress (frames_received / frames_required / is_complete)
1039
+ */
1040
+ async v2Upload(frames, options) {
1041
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
1042
+ const request = {
1043
+ session_id: options.sessionId,
1044
+ model: options.model ?? "10",
1045
+ source: options.source ?? "live",
1046
+ frames: toV2UploadFrameData(frames, { withLandmarks: options.withLandmarks }),
1047
+ ...options.fps != null ? { fps: options.fps } : {},
1048
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
1049
+ ...options.warnings?.length ? { warnings: options.warnings } : {}
1050
+ };
1051
+ return this.requestWithRetry(API_PATHS.v2Upload, {
1052
+ method: "POST",
1053
+ body: JSON.stringify(request),
1054
+ headers: this.buildModelVersionHeaders(effectiveVersion)
1055
+ });
1056
+ }
1057
+ /**
1058
+ * Run a fast-check against frames previously uploaded via {@link v2Upload}
1059
+ * (POST /api/v2/fast-check). The request carries NO frames — the server
1060
+ * resolves them by `session_id`.
1061
+ *
1062
+ * @param options - Session and request context
1063
+ * @returns Liveness result
1064
+ */
1065
+ async v2FastCheck(options) {
1066
+ return this.runV2Check(API_PATHS.v2FastCheck, options);
1067
+ }
1068
+ /**
1069
+ * Run a live-check against frames previously uploaded via {@link v2Upload}
1070
+ * with `withLandmarks: true` (POST /api/v2/live-check). The request carries
1071
+ * NO frames. The server requires a V3 model and surfaces the V3 `diagnostics`
1072
+ * block on the result.
1073
+ *
1074
+ * @param options - Session and request context
1075
+ * @returns Liveness result (includes `diagnostics`)
1076
+ */
1077
+ async v2LiveCheck(options) {
1078
+ return this.runV2Check(API_PATHS.v2LiveCheck, options);
1079
+ }
1080
+ /**
1081
+ * Shared body for the v2 fast-check / live-check requests (internal).
1082
+ * Both endpoints take the identical no-frames payload and response shape;
1083
+ * only the path differs.
1084
+ */
1085
+ async runV2Check(path, options) {
1086
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
1087
+ const di = await this.getDeviceIntelligence();
1088
+ const metadata = this.buildMetadata();
1089
+ const request = {
1090
+ session_id: options.sessionId,
1091
+ model: options.model ?? "10",
1092
+ source: options.source ?? "live",
1093
+ ...options.fps != null ? { fps: options.fps } : {},
1094
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
1095
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
1096
+ ...di ? { device_intelligence: di } : {},
1097
+ ...metadata ? { metadata } : {}
1098
+ };
1099
+ const { data: response, headers } = await this.requestWithRetryRaw(path, {
1100
+ method: "POST",
1101
+ body: JSON.stringify(request),
1102
+ headers: this.buildModelVersionHeaders(effectiveVersion)
1103
+ });
1104
+ const result = toLivenessResult(response);
1105
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
1106
+ if (result.deprecation?.deprecated) {
1107
+ console.warn(
1108
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
1109
+ );
1110
+ }
1111
+ return result;
1112
+ }
918
1113
  };
919
1114
 
920
1115
  // src/buffer/FrameBuffer.ts
@@ -2319,6 +2514,85 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2319
2514
  message: null
2320
2515
  };
2321
2516
  }
2517
+
2518
+ // src/forensic/recorder.ts
2519
+ var RunningMean = class {
2520
+ constructor() {
2521
+ this.sum = 0;
2522
+ this.n = 0;
2523
+ }
2524
+ add(v) {
2525
+ if (typeof v === "number" && !Number.isNaN(v)) {
2526
+ this.sum += v;
2527
+ this.n += 1;
2528
+ }
2529
+ }
2530
+ get value() {
2531
+ return this.n ? this.sum / this.n : void 0;
2532
+ }
2533
+ };
2534
+ var ForensicRecorder = class {
2535
+ constructor(opts = {}) {
2536
+ this.framesCaptured = 0;
2537
+ this.landmarksPresent = 0;
2538
+ this.landmarksValid = 0;
2539
+ this.qualityTicks = 0;
2540
+ this.bytesTotal = 0;
2541
+ this.alignment = new RunningMean();
2542
+ this.blur = new RunningMean();
2543
+ this.brightness = new RunningMean();
2544
+ this.source = opts.source ?? "webcam";
2545
+ this.traceId = opts.traceId ?? generateTraceId();
2546
+ this.ctx = opts.ctx ?? {};
2547
+ }
2548
+ /** Record one captured frame (counts, landmark presence, encoded size). */
2549
+ onFrame(frame) {
2550
+ this.framesCaptured += 1;
2551
+ if (frame.landmarks && frame.landmarks.length) this.landmarksPresent += 1;
2552
+ if (typeof frame.pixels === "string") this.bytesTotal += frame.pixels.length;
2553
+ }
2554
+ /** Record one quality update (alignment/blur/brightness/landmark validity). */
2555
+ onQuality(q) {
2556
+ this.qualityTicks += 1;
2557
+ this.alignment.add(q.alignment);
2558
+ this.blur.add(q.blurVariance);
2559
+ this.brightness.add(q.brightness);
2560
+ if (q.landmarksValid) this.landmarksValid += 1;
2561
+ if (q.encoding) this.encoding = q.encoding;
2562
+ if (q.width) this.width = q.width;
2563
+ if (q.height) this.height = q.height;
2564
+ }
2565
+ /** Record the transport leg once the request is built/sent. */
2566
+ setTransport(t) {
2567
+ this.framesSent = t.framesSent;
2568
+ this.requestBytes = t.requestBytes;
2569
+ this.sessionId = t.sessionId;
2570
+ }
2571
+ /** Summarise the session into the CaptureSummary the fragment builder expects. */
2572
+ summary() {
2573
+ return {
2574
+ framesCaptured: this.framesCaptured,
2575
+ alignmentMean: this.alignment.value,
2576
+ blurVarMean: this.blur.value,
2577
+ brightnessMean: this.brightness.value,
2578
+ landmarksPresentPct: this.framesCaptured ? this.landmarksPresent / this.framesCaptured : void 0,
2579
+ // validity is a fraction of QUALITY ticks, not frames — quality updates fire
2580
+ // more often than frames, so dividing by frames could exceed 1.0.
2581
+ validationPassPct: this.qualityTicks ? this.landmarksValid / this.qualityTicks : void 0,
2582
+ encoding: this.encoding,
2583
+ width: this.width,
2584
+ height: this.height,
2585
+ bytesPerFrame: this.framesCaptured && this.bytesTotal ? Math.round(this.bytesTotal / this.framesCaptured) : void 0,
2586
+ framesSent: this.framesSent ?? (this.framesCaptured || void 0),
2587
+ requestBytes: this.requestBytes,
2588
+ sessionId: this.sessionId
2589
+ };
2590
+ }
2591
+ /** Build the client fragment for this session. */
2592
+ build() {
2593
+ return buildClientFragment(this.traceId, this.source, this.summary(), this.ctx);
2594
+ }
2595
+ };
2322
2596
  export {
2323
2597
  ALIGNMENT_THRESHOLD_CAPTURE,
2324
2598
  ALIGNMENT_THRESHOLD_GOOD,
@@ -2353,8 +2627,10 @@ export {
2353
2627
  FACE_CROP_FRAME_MARGIN,
2354
2628
  FACE_CROP_OUTPUT_SIZE,
2355
2629
  FEEDBACK_MESSAGES,
2630
+ FORENSIC_SCHEMA_VERSION,
2356
2631
  FRAME_BUFFER_CONFIG,
2357
2632
  FRAME_CONFIG,
2633
+ ForensicRecorder,
2358
2634
  FrameBuffer,
2359
2635
  FrameQueue,
2360
2636
  GOOD_ALIGNMENT,
@@ -2392,6 +2668,7 @@ export {
2392
2668
  analyzeEyeRegionBrightness,
2393
2669
  analyzeEyeRegionContrast,
2394
2670
  analyzeLighting,
2671
+ buildClientFragment,
2395
2672
  calculateBrightness,
2396
2673
  calculateFaceAlignment,
2397
2674
  calculateFaceCropRegion,
@@ -2406,6 +2683,7 @@ export {
2406
2683
  detectSpecularHighlights,
2407
2684
  encodeBase64,
2408
2685
  generateSessionId,
2686
+ generateTraceId,
2409
2687
  getActiveModels,
2410
2688
  getApiErrorMessage,
2411
2689
  getCaptureQualityFeedback,
@@ -2429,6 +2707,7 @@ export {
2429
2707
  toHybridFrameData,
2430
2708
  toLivenessResult,
2431
2709
  toLivenessResultFromStream,
2710
+ toV2UploadFrameData,
2432
2711
  validateApiKey,
2433
2712
  validateFaceLandmarks,
2434
2713
  validateFrameCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "3.13.0",
3
+ "version": "3.16.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "author": "Moveris - Jeyson Palacio, Arthur ITurres, Eric D. Brown",
6
6
  "main": "./dist/index.js",