@moveris/shared 3.6.1 → 3.8.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
@@ -30,7 +30,7 @@ const result = await client.fastCheck(frames, {
30
30
  source: 'live',
31
31
  });
32
32
 
33
- console.log(result.verdict); // 'live' or 'fake'
33
+ console.log(result.verdict); // 'live', 'fake', or 'inconclusive'
34
34
  console.log(result.confidence); // 0-1
35
35
  console.log(result.score); // 0-100
36
36
  ```
@@ -47,14 +47,15 @@ The main client for interacting with the Moveris Liveness API.
47
47
  const client = new LivenessClient(config: LivenessClientConfig);
48
48
  ```
49
49
 
50
- | Option | Type | Default | Description |
51
- | -------------- | -------------- | --------------------------- | ------------------------------------------------------------------ |
52
- | `apiKey` | `string` | **required** | Your Moveris API key |
53
- | `baseUrl` | `string` | `'https://api.moveris.com'` | API base URL |
54
- | `modelVersion` | `ModelVersion` | - | Model version alias for `X-Model-Version` header (e.g. `'latest'`) |
55
- | `timeout` | `number` | `30000` | Request timeout in milliseconds |
56
- | `enableRetry` | `boolean` | `true` | Enable automatic retry with exponential backoff |
57
- | `customFetch` | `typeof fetch` | `fetch` | Custom fetch implementation (for React Native) |
50
+ | Option | Type | Default | Description |
51
+ | ----------------------------- | ----------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
52
+ | `apiKey` | `string` | **required** | Your Moveris API key |
53
+ | `baseUrl` | `string` | `'https://api.moveris.com'` | API base URL |
54
+ | `modelVersion` | `ModelVersion` | - | Model version alias for `X-Model-Version` header (e.g. `'latest'`) |
55
+ | `timeout` | `number` | `30000` | Request timeout in milliseconds |
56
+ | `enableRetry` | `boolean` | `true` | Enable automatic retry with exponential backoff |
57
+ | `customFetch` | `typeof fetch` | `fetch` | Custom fetch implementation (for React Native) |
58
+ | `deviceIntelligenceOverrides` | `DeviceIntelligenceOverrides` | - | Static overrides merged into every `device_intelligence` payload (e.g. `{ vpn_detected: true }`) |
58
59
 
59
60
  #### Methods
60
61
 
@@ -151,6 +152,17 @@ const result = await client.hybrid50(frames, { fps: 30 });
151
152
  const result = await client.hybrid150(frames, { fps: 30 });
152
153
  ```
153
154
 
155
+ ##### `updateDeviceIntelligenceOverrides(overrides)`
156
+
157
+ 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.
158
+
159
+ ```typescript
160
+ client.updateDeviceIntelligenceOverrides({
161
+ camera: { device_name: 'FaceTime HD Camera', resolution: '1280x720' },
162
+ });
163
+ client.updateDeviceIntelligenceOverrides({ vpn_detected: true });
164
+ ```
165
+
154
166
  ##### `health()`
155
167
 
156
168
  Check API health status.
@@ -260,13 +272,14 @@ type FrameSource = 'media' | 'live';
260
272
  Liveness determination result.
261
273
 
262
274
  ```typescript
263
- type Verdict = 'live' | 'fake';
275
+ type Verdict = 'live' | 'fake' | 'inconclusive';
264
276
  ```
265
277
 
266
- | Value | Description |
267
- | -------- | ---------------------------------------------------- |
268
- | `'live'` | Real person detected |
269
- | `'fake'` | Spoofing attempt detected (photo, video, mask, etc.) |
278
+ | Value | Description |
279
+ | ---------------- | -------------------------------------------------------------------------------- |
280
+ | `'live'` | Real person detected |
281
+ | `'fake'` | Spoofing attempt detected (photo, video, mask, etc.) |
282
+ | `'inconclusive'` | API processed successfully but could not determine a verdict — retry recommended |
270
283
 
271
284
  #### LivenessState
272
285
 
@@ -288,7 +301,7 @@ Result returned from liveness verification.
288
301
 
289
302
  ```typescript
290
303
  interface LivenessResult {
291
- verdict: Verdict; // 'live' or 'fake'
304
+ verdict: Verdict; // 'live', 'fake', or 'inconclusive'
292
305
  confidence: number; // Confidence score (0-1)
293
306
  score: number; // Liveness score (0-100)
294
307
  sessionId: string; // Session identifier
@@ -725,6 +738,91 @@ const active = getActiveModels();
725
738
 
726
739
  ---
727
740
 
741
+ ## Device Intelligence
742
+
743
+ The SDK automatically collects device metadata and attaches it to every API request as `device_intelligence`. Collection happens inside `LivenessClient` — no extra work required for standard consumers.
744
+
745
+ ### `collectDeviceIntelligence(opts?)`
746
+
747
+ Collect device metadata manually. Useful for Approach B consumers (custom UI + raw `fetch()`) who bypass `LivenessClient`.
748
+
749
+ ```typescript
750
+ import { collectDeviceIntelligence } from '@moveris/shared';
751
+
752
+ const di = await collectDeviceIntelligence({
753
+ platformVersion: 'shared@3.6.2', // optional — defaults to SHARED_SDK_PLATFORM
754
+ overrides: { vpn_detected: true }, // optional — merged on top of collected data
755
+ });
756
+
757
+ if (di) {
758
+ console.log(di.ip); // '1.2.3.4'
759
+ console.log(di.country); // 'US'
760
+ console.log(di.isp); // 'Comcast Cable'
761
+ console.log(di.platform_version); // 'shared@3.6.2'
762
+ }
763
+ // Returns null if ipinfo.io is unreachable — handle gracefully
764
+ ```
765
+
766
+ > If you import from `@moveris/react`, use that package's `collectDeviceIntelligence` instead — it pre-bakes `REACT_SDK_PLATFORM` as the default `platformVersion` so the full `react@A.B.C+shared@X.Y.Z` string is used automatically.
767
+
768
+ ### `SHARED_SDK_PLATFORM`
769
+
770
+ Pre-formatted platform string for the `platform_version` field. Reads the version from `package.json` at build time — never goes stale.
771
+
772
+ ```typescript
773
+ import { SHARED_SDK_PLATFORM } from '@moveris/shared';
774
+
775
+ console.log(SHARED_SDK_PLATFORM); // 'shared@3.6.2'
776
+ ```
777
+
778
+ ### Device Intelligence Types
779
+
780
+ ```typescript
781
+ import type {
782
+ DeviceIntelligence,
783
+ DeviceIntelligenceOverrides,
784
+ DeviceIntelligenceGeo,
785
+ DeviceIntelligenceCamera,
786
+ } from '@moveris/shared';
787
+ ```
788
+
789
+ ```typescript
790
+ interface DeviceIntelligence {
791
+ ip: string;
792
+ country: string; // ISO 3166-1 alpha-2, e.g. 'US'
793
+ region: string; // e.g. 'California'
794
+ city: string; // e.g. 'San Francisco'
795
+ isp?: string; // Internet service provider
796
+ user_agent?: string; // navigator.userAgent
797
+ platform_version?: string; // SDK version string
798
+ vpn_detected?: boolean;
799
+ camera?: DeviceIntelligenceCamera;
800
+ }
801
+
802
+ interface DeviceIntelligenceCamera {
803
+ device_name?: string; // e.g. 'FaceTime HD Camera'
804
+ resolution?: string; // e.g. '1280x720'
805
+ }
806
+
807
+ interface DeviceIntelligenceOverrides {
808
+ vpn_detected?: boolean;
809
+ camera?: DeviceIntelligenceCamera;
810
+ [key: string]: unknown;
811
+ }
812
+ ```
813
+
814
+ ### CSP Note
815
+
816
+ If your app enforces a Content Security Policy, add `https://ipinfo.io` to `connect-src`:
817
+
818
+ ```
819
+ connect-src 'self' https://api.moveris.com https://ipinfo.io;
820
+ ```
821
+
822
+ Without this, the ipinfo.io fetch will be blocked silently and `device_intelligence` will be omitted from all requests.
823
+
824
+ ---
825
+
728
826
  ## Configuration Constants
729
827
 
730
828
  ```typescript
@@ -766,6 +864,11 @@ import type {
766
864
  EyeQualityThresholds,
767
865
  EyeRegionBounds,
768
866
  EyeRegionsBounds,
867
+ // Device Intelligence Types
868
+ DeviceIntelligence,
869
+ DeviceIntelligenceOverrides,
870
+ DeviceIntelligenceGeo,
871
+ DeviceIntelligenceCamera,
769
872
  } from '@moveris/shared';
770
873
  ```
771
874
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,31 @@
1
- type Verdict = 'live' | 'fake';
1
+ interface DeviceIntelligenceGeo {
2
+ country: string;
3
+ region: string;
4
+ city: string;
5
+ isp?: string;
6
+ }
7
+ interface DeviceIntelligenceCamera {
8
+ device_name: string;
9
+ resolution: string;
10
+ }
11
+ interface DeviceIntelligence {
12
+ ip_address: string;
13
+ geo: DeviceIntelligenceGeo;
14
+ vpn_detected: boolean;
15
+ camera: DeviceIntelligenceCamera;
16
+ user_agent: string;
17
+ platform_version: string;
18
+ }
19
+ type DeviceIntelligenceOverrides = {
20
+ ip_address?: string;
21
+ geo?: Partial<DeviceIntelligenceGeo>;
22
+ vpn_detected?: boolean;
23
+ camera?: Partial<DeviceIntelligenceCamera>;
24
+ user_agent?: string;
25
+ platform_version?: string;
26
+ };
27
+
28
+ type Verdict = 'live' | 'fake' | 'inconclusive';
2
29
  interface ModelEntry {
3
30
  id: string;
4
31
  label: string;
@@ -40,6 +67,7 @@ interface FastCheckRequest {
40
67
  frames: FrameData[];
41
68
  frame_count?: number;
42
69
  warnings?: string[];
70
+ device_intelligence?: DeviceIntelligence;
43
71
  }
44
72
  interface FastCheckCropsRequest {
45
73
  session_id: string;
@@ -47,6 +75,7 @@ interface FastCheckCropsRequest {
47
75
  source?: FrameSource;
48
76
  crops: CropData[];
49
77
  frame_count?: number;
78
+ device_intelligence?: DeviceIntelligence;
50
79
  }
51
80
  interface VerifyRequest {
52
81
  session_id: string;
@@ -78,6 +107,7 @@ interface FastCheckStreamRequest {
78
107
  frame: FrameData;
79
108
  frame_count?: number;
80
109
  warnings?: string[];
110
+ device_intelligence?: DeviceIntelligence;
81
111
  }
82
112
  interface FastCheckResponse {
83
113
  verdict: Verdict | null;
@@ -539,6 +569,7 @@ interface LivenessClientConfig {
539
569
  timeout?: number;
540
570
  enableRetry?: boolean;
541
571
  customFetch?: typeof fetch;
572
+ deviceIntelligenceOverrides?: DeviceIntelligenceOverrides;
542
573
  }
543
574
  declare class LivenessApiError extends Error {
544
575
  readonly code: string;
@@ -559,7 +590,13 @@ declare class LivenessClient {
559
590
  private readonly timeout;
560
591
  private readonly enableRetry;
561
592
  private readonly fetchFn;
593
+ private diCollected;
594
+ private diCollecting;
595
+ private diOverrides;
562
596
  constructor(config: LivenessClientConfig);
597
+ updateDeviceIntelligenceOverrides(partial: DeviceIntelligenceOverrides): void;
598
+ private getDeviceIntelligence;
599
+ private applyDiOverrides;
563
600
  private request;
564
601
  private requestRaw;
565
602
  private parseErrorResponse;
@@ -902,4 +939,10 @@ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray
902
939
  declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
903
940
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
904
941
 
905
- 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 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, 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, 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 };
942
+ declare const SHARED_SDK_PLATFORM: string;
943
+ declare function collectDeviceIntelligence(opts?: {
944
+ overrides?: DeviceIntelligenceOverrides;
945
+ platformVersion?: string;
946
+ }): Promise<DeviceIntelligence | null>;
947
+
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 };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,31 @@
1
- type Verdict = 'live' | 'fake';
1
+ interface DeviceIntelligenceGeo {
2
+ country: string;
3
+ region: string;
4
+ city: string;
5
+ isp?: string;
6
+ }
7
+ interface DeviceIntelligenceCamera {
8
+ device_name: string;
9
+ resolution: string;
10
+ }
11
+ interface DeviceIntelligence {
12
+ ip_address: string;
13
+ geo: DeviceIntelligenceGeo;
14
+ vpn_detected: boolean;
15
+ camera: DeviceIntelligenceCamera;
16
+ user_agent: string;
17
+ platform_version: string;
18
+ }
19
+ type DeviceIntelligenceOverrides = {
20
+ ip_address?: string;
21
+ geo?: Partial<DeviceIntelligenceGeo>;
22
+ vpn_detected?: boolean;
23
+ camera?: Partial<DeviceIntelligenceCamera>;
24
+ user_agent?: string;
25
+ platform_version?: string;
26
+ };
27
+
28
+ type Verdict = 'live' | 'fake' | 'inconclusive';
2
29
  interface ModelEntry {
3
30
  id: string;
4
31
  label: string;
@@ -40,6 +67,7 @@ interface FastCheckRequest {
40
67
  frames: FrameData[];
41
68
  frame_count?: number;
42
69
  warnings?: string[];
70
+ device_intelligence?: DeviceIntelligence;
43
71
  }
44
72
  interface FastCheckCropsRequest {
45
73
  session_id: string;
@@ -47,6 +75,7 @@ interface FastCheckCropsRequest {
47
75
  source?: FrameSource;
48
76
  crops: CropData[];
49
77
  frame_count?: number;
78
+ device_intelligence?: DeviceIntelligence;
50
79
  }
51
80
  interface VerifyRequest {
52
81
  session_id: string;
@@ -78,6 +107,7 @@ interface FastCheckStreamRequest {
78
107
  frame: FrameData;
79
108
  frame_count?: number;
80
109
  warnings?: string[];
110
+ device_intelligence?: DeviceIntelligence;
81
111
  }
82
112
  interface FastCheckResponse {
83
113
  verdict: Verdict | null;
@@ -539,6 +569,7 @@ interface LivenessClientConfig {
539
569
  timeout?: number;
540
570
  enableRetry?: boolean;
541
571
  customFetch?: typeof fetch;
572
+ deviceIntelligenceOverrides?: DeviceIntelligenceOverrides;
542
573
  }
543
574
  declare class LivenessApiError extends Error {
544
575
  readonly code: string;
@@ -559,7 +590,13 @@ declare class LivenessClient {
559
590
  private readonly timeout;
560
591
  private readonly enableRetry;
561
592
  private readonly fetchFn;
593
+ private diCollected;
594
+ private diCollecting;
595
+ private diOverrides;
562
596
  constructor(config: LivenessClientConfig);
597
+ updateDeviceIntelligenceOverrides(partial: DeviceIntelligenceOverrides): void;
598
+ private getDeviceIntelligence;
599
+ private applyDiOverrides;
563
600
  private request;
564
601
  private requestRaw;
565
602
  private parseErrorResponse;
@@ -902,4 +939,10 @@ declare function analyzeEyeRegionContrast(pixels: Uint8Array | Uint8ClampedArray
902
939
  declare function detectSpecularHighlights(pixels: Uint8Array | Uint8ClampedArray, relativeFactor?: number, meanBrightness?: number): number;
903
940
  declare function checkEyeRegionQuality(pixels: Uint8Array | Uint8ClampedArray, thresholds?: EyeQualityThresholds): EyeRegionQuality;
904
941
 
905
- 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 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, 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, 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 };
942
+ declare const SHARED_SDK_PLATFORM: string;
943
+ declare function collectDeviceIntelligence(opts?: {
944
+ overrides?: DeviceIntelligenceOverrides;
945
+ platformVersion?: string;
946
+ }): Promise<DeviceIntelligence | null>;
947
+
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 };
package/dist/index.js CHANGED
@@ -87,6 +87,7 @@ __export(index_exports, {
87
87
  OVAL_REGION_DESKTOP: () => OVAL_REGION_DESKTOP,
88
88
  OVAL_REGION_MOBILE: () => OVAL_REGION_MOBILE,
89
89
  RETRY_CONFIG: () => RETRY_CONFIG,
90
+ SHARED_SDK_PLATFORM: () => SHARED_SDK_PLATFORM,
90
91
  TARGET_FACE_PERCENTAGE_IN_CROP: () => TARGET_FACE_PERCENTAGE_IN_CROP,
91
92
  VALID_FRAME_COUNTS: () => VALID_FRAME_COUNTS,
92
93
  analyzeBlur: () => analyzeBlur,
@@ -101,6 +102,7 @@ __export(index_exports, {
101
102
  canCaptureFrame: () => canCaptureFrame,
102
103
  checkEyeRegionQuality: () => checkEyeRegionQuality,
103
104
  checkFrameQuality: () => checkFrameQuality,
105
+ collectDeviceIntelligence: () => collectDeviceIntelligence,
104
106
  decodeBase64: () => decodeBase64,
105
107
  detectCameraAngle: () => detectCameraAngle,
106
108
  detectFaceRoll: () => detectFaceRoll,
@@ -239,6 +241,55 @@ async function sleep(ms) {
239
241
  await new Promise((resolve) => setTimeout(resolve, ms));
240
242
  }
241
243
 
244
+ // package.json
245
+ var version = "3.8.0";
246
+
247
+ // src/utils/deviceIntelligence.ts
248
+ var IPINFO_URL = "https://ipinfo.io/json";
249
+ var SHARED_SDK_VERSION = version;
250
+ var SHARED_SDK_PLATFORM = `shared@${SHARED_SDK_VERSION}`;
251
+ function parseIsp(org) {
252
+ if (!org) return void 0;
253
+ return org.replace(/^AS\d+\s+/, "").trim() || void 0;
254
+ }
255
+ function mergeOverrides(collected, overrides) {
256
+ if (!overrides) return collected;
257
+ return {
258
+ ...collected,
259
+ ...overrides,
260
+ geo: { ...collected.geo, ...overrides.geo },
261
+ camera: { ...collected.camera, ...overrides.camera }
262
+ };
263
+ }
264
+ async function collectDeviceIntelligence(opts) {
265
+ try {
266
+ const response = await fetch(IPINFO_URL);
267
+ if (!response.ok) return null;
268
+ const data = await response.json();
269
+ if (!data.ip) return null;
270
+ const isp = parseIsp(data.org);
271
+ const collected = {
272
+ ip_address: data.ip,
273
+ geo: {
274
+ country: data.country ?? "Unknown",
275
+ region: data.region ?? "Unknown",
276
+ city: data.city ?? "Unknown",
277
+ ...isp ? { isp } : {}
278
+ },
279
+ vpn_detected: false,
280
+ camera: {
281
+ device_name: "Unknown",
282
+ resolution: "Unknown"
283
+ },
284
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "Unknown",
285
+ platform_version: opts?.platformVersion ?? SHARED_SDK_PLATFORM
286
+ };
287
+ return mergeOverrides(collected, opts?.overrides);
288
+ } catch {
289
+ return null;
290
+ }
291
+ }
292
+
242
293
  // src/client/LivenessClient.ts
243
294
  var LivenessApiError = class extends Error {
244
295
  constructor(message, code, statusCode, required, received) {
@@ -265,7 +316,18 @@ function toHybridFrameData(frames) {
265
316
  }
266
317
  function toLivenessResult(response) {
267
318
  if (!response.verdict) {
268
- throw new LivenessApiError(response.error ?? "No verdict received", "no_verdict", 500);
319
+ if (response.error) {
320
+ throw new LivenessApiError(response.error, "no_verdict", 500);
321
+ }
322
+ return {
323
+ verdict: "inconclusive",
324
+ confidence: response.confidence ?? 0,
325
+ score: response.score ?? 0,
326
+ sessionId: response.session_id,
327
+ processingMs: response.processing_ms,
328
+ framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
329
+ warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
330
+ };
269
331
  }
270
332
  return {
271
333
  verdict: response.verdict,
@@ -282,8 +344,18 @@ function toLivenessResultFromStream(response) {
282
344
  throw new LivenessApiError("Stream not complete", "stream_incomplete", 400);
283
345
  }
284
346
  if (!response.verdict) {
285
- const errorCode = response.error ?? "no_verdict";
286
- throw new LivenessApiError(response.error ?? "No verdict received", errorCode, 500);
347
+ if (response.error) {
348
+ throw new LivenessApiError(response.error, response.error, 500);
349
+ }
350
+ return {
351
+ verdict: "inconclusive",
352
+ confidence: response.confidence ?? 0,
353
+ score: response.score ?? 0,
354
+ sessionId: response.session_id,
355
+ processingMs: response.processing_ms ?? 0,
356
+ framesProcessed: response.frames_processed ?? 0,
357
+ warnings: Array.isArray(response.warnings) ? response.warnings : void 0
358
+ };
287
359
  }
288
360
  return {
289
361
  verdict: response.verdict,
@@ -307,12 +379,58 @@ function generateSessionId() {
307
379
  }
308
380
  var LivenessClient = class _LivenessClient {
309
381
  constructor(config) {
382
+ // Device intelligence: lazily collected on first API call, then cached.
383
+ // undefined = not yet attempted, null = collection failed.
384
+ this.diCollected = void 0;
385
+ this.diCollecting = null;
310
386
  this.baseUrl = (config.baseUrl ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
311
387
  this.apiKey = config.apiKey;
312
388
  this.modelVersion = config.modelVersion;
313
389
  this.timeout = config.timeout ?? AUTH_CONFIG.timeout;
314
390
  this.enableRetry = config.enableRetry ?? true;
315
391
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
392
+ this.diOverrides = { ...config.deviceIntelligenceOverrides };
393
+ }
394
+ /**
395
+ * Merge additional device intelligence overrides into the existing set.
396
+ * Called by the React layer to inject camera info once the stream is active.
397
+ * Overrides deep-merge — only specified keys are replaced.
398
+ */
399
+ updateDeviceIntelligenceOverrides(partial) {
400
+ this.diOverrides = {
401
+ ...this.diOverrides,
402
+ ...partial,
403
+ ...partial.geo ? { geo: { ...this.diOverrides.geo, ...partial.geo } } : {},
404
+ ...partial.camera ? { camera: { ...this.diOverrides.camera, ...partial.camera } } : {}
405
+ };
406
+ }
407
+ /**
408
+ * Return the merged device intelligence (auto-collected + overrides).
409
+ * Collection is lazy — runs once on first call, then returns cached result.
410
+ * Returns null if collection failed; callers should omit the field in that case.
411
+ */
412
+ async getDeviceIntelligence() {
413
+ if (this.diCollected !== void 0) {
414
+ return this.diCollected ? this.applyDiOverrides(this.diCollected) : null;
415
+ }
416
+ if (!this.diCollecting) {
417
+ this.diCollecting = collectDeviceIntelligence({
418
+ platformVersion: SHARED_SDK_PLATFORM
419
+ }).then((result) => {
420
+ this.diCollected = result ?? null;
421
+ });
422
+ }
423
+ await this.diCollecting;
424
+ return this.diCollected ? this.applyDiOverrides(this.diCollected) : null;
425
+ }
426
+ applyDiOverrides(collected) {
427
+ const ov = this.diOverrides;
428
+ return {
429
+ ...collected,
430
+ ...ov,
431
+ geo: { ...collected.geo, ...ov.geo },
432
+ camera: { ...collected.camera, ...ov.camera }
433
+ };
316
434
  }
317
435
  /**
318
436
  * Make an authenticated API request, returning the parsed body.
@@ -465,9 +583,9 @@ var LivenessClient = class _LivenessClient {
465
583
  * Build extra request headers for model versioning.
466
584
  */
467
585
  buildModelVersionHeaders(modelVersion) {
468
- const version = modelVersion ?? this.modelVersion;
469
- if (!version) return {};
470
- return { [AUTH_CONFIG.modelVersionHeader]: version };
586
+ const version2 = modelVersion ?? this.modelVersion;
587
+ if (!version2) return {};
588
+ return { [AUTH_CONFIG.modelVersionHeader]: version2 };
471
589
  }
472
590
  /**
473
591
  * Make a request with optional retry
@@ -532,13 +650,15 @@ var LivenessClient = class _LivenessClient {
532
650
  */
533
651
  async fastCheck(frames, options = {}) {
534
652
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
653
+ const di = await this.getDeviceIntelligence();
535
654
  const request = {
536
655
  session_id: options.sessionId ?? generateSessionId(),
537
656
  model: options.model ?? "10",
538
657
  source: options.source ?? "live",
539
658
  frames: toFrameData(frames),
540
659
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
541
- ...options.warnings?.length ? { warnings: options.warnings } : {}
660
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
661
+ ...di ? { device_intelligence: di } : {}
542
662
  };
543
663
  const { data: response, headers } = await this.requestWithRetryRaw(
544
664
  API_PATHS.fastCheck,
@@ -566,6 +686,7 @@ var LivenessClient = class _LivenessClient {
566
686
  */
567
687
  async fastCheckCrops(crops, options = {}) {
568
688
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
689
+ const di = await this.getDeviceIntelligence();
569
690
  const request = {
570
691
  session_id: options.sessionId ?? generateSessionId(),
571
692
  model: options.model ?? "10",
@@ -573,7 +694,8 @@ var LivenessClient = class _LivenessClient {
573
694
  crops,
574
695
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
575
696
  ...options.warnings?.length ? { warnings: options.warnings } : {},
576
- ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
697
+ ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {},
698
+ ...di ? { device_intelligence: di } : {}
577
699
  };
578
700
  const { data: response, headers } = await this.requestWithRetryRaw(
579
701
  API_PATHS.fastCheckCrops,
@@ -629,13 +751,15 @@ var LivenessClient = class _LivenessClient {
629
751
  */
630
752
  async sendStreamFrameInternal(frameData, options) {
631
753
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
754
+ const di = await this.getDeviceIntelligence();
632
755
  const request = {
633
756
  session_id: options.sessionId,
634
757
  model: options.model,
635
758
  source: options.source,
636
759
  frame: frameData,
637
760
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
638
- ...options.warnings?.length ? { warnings: options.warnings } : {}
761
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
762
+ ...di ? { device_intelligence: di } : {}
639
763
  };
640
764
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
641
765
  method: "POST",
@@ -2293,6 +2417,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2293
2417
  OVAL_REGION_DESKTOP,
2294
2418
  OVAL_REGION_MOBILE,
2295
2419
  RETRY_CONFIG,
2420
+ SHARED_SDK_PLATFORM,
2296
2421
  TARGET_FACE_PERCENTAGE_IN_CROP,
2297
2422
  VALID_FRAME_COUNTS,
2298
2423
  analyzeBlur,
@@ -2307,6 +2432,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2307
2432
  canCaptureFrame,
2308
2433
  checkEyeRegionQuality,
2309
2434
  checkFrameQuality,
2435
+ collectDeviceIntelligence,
2310
2436
  decodeBase64,
2311
2437
  detectCameraAngle,
2312
2438
  detectFaceRoll,
package/dist/index.mjs CHANGED
@@ -95,6 +95,55 @@ async function sleep(ms) {
95
95
  await new Promise((resolve) => setTimeout(resolve, ms));
96
96
  }
97
97
 
98
+ // package.json
99
+ var version = "3.8.0";
100
+
101
+ // src/utils/deviceIntelligence.ts
102
+ var IPINFO_URL = "https://ipinfo.io/json";
103
+ var SHARED_SDK_VERSION = version;
104
+ var SHARED_SDK_PLATFORM = `shared@${SHARED_SDK_VERSION}`;
105
+ function parseIsp(org) {
106
+ if (!org) return void 0;
107
+ return org.replace(/^AS\d+\s+/, "").trim() || void 0;
108
+ }
109
+ function mergeOverrides(collected, overrides) {
110
+ if (!overrides) return collected;
111
+ return {
112
+ ...collected,
113
+ ...overrides,
114
+ geo: { ...collected.geo, ...overrides.geo },
115
+ camera: { ...collected.camera, ...overrides.camera }
116
+ };
117
+ }
118
+ async function collectDeviceIntelligence(opts) {
119
+ try {
120
+ const response = await fetch(IPINFO_URL);
121
+ if (!response.ok) return null;
122
+ const data = await response.json();
123
+ if (!data.ip) return null;
124
+ const isp = parseIsp(data.org);
125
+ const collected = {
126
+ ip_address: data.ip,
127
+ geo: {
128
+ country: data.country ?? "Unknown",
129
+ region: data.region ?? "Unknown",
130
+ city: data.city ?? "Unknown",
131
+ ...isp ? { isp } : {}
132
+ },
133
+ vpn_detected: false,
134
+ camera: {
135
+ device_name: "Unknown",
136
+ resolution: "Unknown"
137
+ },
138
+ user_agent: typeof navigator !== "undefined" ? navigator.userAgent : "Unknown",
139
+ platform_version: opts?.platformVersion ?? SHARED_SDK_PLATFORM
140
+ };
141
+ return mergeOverrides(collected, opts?.overrides);
142
+ } catch {
143
+ return null;
144
+ }
145
+ }
146
+
98
147
  // src/client/LivenessClient.ts
99
148
  var LivenessApiError = class extends Error {
100
149
  constructor(message, code, statusCode, required, received) {
@@ -121,7 +170,18 @@ function toHybridFrameData(frames) {
121
170
  }
122
171
  function toLivenessResult(response) {
123
172
  if (!response.verdict) {
124
- throw new LivenessApiError(response.error ?? "No verdict received", "no_verdict", 500);
173
+ if (response.error) {
174
+ throw new LivenessApiError(response.error, "no_verdict", 500);
175
+ }
176
+ return {
177
+ verdict: "inconclusive",
178
+ confidence: response.confidence ?? 0,
179
+ score: response.score ?? 0,
180
+ sessionId: response.session_id,
181
+ processingMs: response.processing_ms,
182
+ framesProcessed: "frames_processed" in response ? response.frames_processed ?? 0 : 0,
183
+ warnings: "warnings" in response && Array.isArray(response.warnings) ? response.warnings : void 0
184
+ };
125
185
  }
126
186
  return {
127
187
  verdict: response.verdict,
@@ -138,8 +198,18 @@ function toLivenessResultFromStream(response) {
138
198
  throw new LivenessApiError("Stream not complete", "stream_incomplete", 400);
139
199
  }
140
200
  if (!response.verdict) {
141
- const errorCode = response.error ?? "no_verdict";
142
- throw new LivenessApiError(response.error ?? "No verdict received", errorCode, 500);
201
+ if (response.error) {
202
+ throw new LivenessApiError(response.error, response.error, 500);
203
+ }
204
+ return {
205
+ verdict: "inconclusive",
206
+ confidence: response.confidence ?? 0,
207
+ score: response.score ?? 0,
208
+ sessionId: response.session_id,
209
+ processingMs: response.processing_ms ?? 0,
210
+ framesProcessed: response.frames_processed ?? 0,
211
+ warnings: Array.isArray(response.warnings) ? response.warnings : void 0
212
+ };
143
213
  }
144
214
  return {
145
215
  verdict: response.verdict,
@@ -163,12 +233,58 @@ function generateSessionId() {
163
233
  }
164
234
  var LivenessClient = class _LivenessClient {
165
235
  constructor(config) {
236
+ // Device intelligence: lazily collected on first API call, then cached.
237
+ // undefined = not yet attempted, null = collection failed.
238
+ this.diCollected = void 0;
239
+ this.diCollecting = null;
166
240
  this.baseUrl = (config.baseUrl ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
167
241
  this.apiKey = config.apiKey;
168
242
  this.modelVersion = config.modelVersion;
169
243
  this.timeout = config.timeout ?? AUTH_CONFIG.timeout;
170
244
  this.enableRetry = config.enableRetry ?? true;
171
245
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
246
+ this.diOverrides = { ...config.deviceIntelligenceOverrides };
247
+ }
248
+ /**
249
+ * Merge additional device intelligence overrides into the existing set.
250
+ * Called by the React layer to inject camera info once the stream is active.
251
+ * Overrides deep-merge — only specified keys are replaced.
252
+ */
253
+ updateDeviceIntelligenceOverrides(partial) {
254
+ this.diOverrides = {
255
+ ...this.diOverrides,
256
+ ...partial,
257
+ ...partial.geo ? { geo: { ...this.diOverrides.geo, ...partial.geo } } : {},
258
+ ...partial.camera ? { camera: { ...this.diOverrides.camera, ...partial.camera } } : {}
259
+ };
260
+ }
261
+ /**
262
+ * Return the merged device intelligence (auto-collected + overrides).
263
+ * Collection is lazy — runs once on first call, then returns cached result.
264
+ * Returns null if collection failed; callers should omit the field in that case.
265
+ */
266
+ async getDeviceIntelligence() {
267
+ if (this.diCollected !== void 0) {
268
+ return this.diCollected ? this.applyDiOverrides(this.diCollected) : null;
269
+ }
270
+ if (!this.diCollecting) {
271
+ this.diCollecting = collectDeviceIntelligence({
272
+ platformVersion: SHARED_SDK_PLATFORM
273
+ }).then((result) => {
274
+ this.diCollected = result ?? null;
275
+ });
276
+ }
277
+ await this.diCollecting;
278
+ return this.diCollected ? this.applyDiOverrides(this.diCollected) : null;
279
+ }
280
+ applyDiOverrides(collected) {
281
+ const ov = this.diOverrides;
282
+ return {
283
+ ...collected,
284
+ ...ov,
285
+ geo: { ...collected.geo, ...ov.geo },
286
+ camera: { ...collected.camera, ...ov.camera }
287
+ };
172
288
  }
173
289
  /**
174
290
  * Make an authenticated API request, returning the parsed body.
@@ -321,9 +437,9 @@ var LivenessClient = class _LivenessClient {
321
437
  * Build extra request headers for model versioning.
322
438
  */
323
439
  buildModelVersionHeaders(modelVersion) {
324
- const version = modelVersion ?? this.modelVersion;
325
- if (!version) return {};
326
- return { [AUTH_CONFIG.modelVersionHeader]: version };
440
+ const version2 = modelVersion ?? this.modelVersion;
441
+ if (!version2) return {};
442
+ return { [AUTH_CONFIG.modelVersionHeader]: version2 };
327
443
  }
328
444
  /**
329
445
  * Make a request with optional retry
@@ -388,13 +504,15 @@ var LivenessClient = class _LivenessClient {
388
504
  */
389
505
  async fastCheck(frames, options = {}) {
390
506
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
507
+ const di = await this.getDeviceIntelligence();
391
508
  const request = {
392
509
  session_id: options.sessionId ?? generateSessionId(),
393
510
  model: options.model ?? "10",
394
511
  source: options.source ?? "live",
395
512
  frames: toFrameData(frames),
396
513
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
397
- ...options.warnings?.length ? { warnings: options.warnings } : {}
514
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
515
+ ...di ? { device_intelligence: di } : {}
398
516
  };
399
517
  const { data: response, headers } = await this.requestWithRetryRaw(
400
518
  API_PATHS.fastCheck,
@@ -422,6 +540,7 @@ var LivenessClient = class _LivenessClient {
422
540
  */
423
541
  async fastCheckCrops(crops, options = {}) {
424
542
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
543
+ const di = await this.getDeviceIntelligence();
425
544
  const request = {
426
545
  session_id: options.sessionId ?? generateSessionId(),
427
546
  model: options.model ?? "10",
@@ -429,7 +548,8 @@ var LivenessClient = class _LivenessClient {
429
548
  crops,
430
549
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
431
550
  ...options.warnings?.length ? { warnings: options.warnings } : {},
432
- ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
551
+ ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {},
552
+ ...di ? { device_intelligence: di } : {}
433
553
  };
434
554
  const { data: response, headers } = await this.requestWithRetryRaw(
435
555
  API_PATHS.fastCheckCrops,
@@ -485,13 +605,15 @@ var LivenessClient = class _LivenessClient {
485
605
  */
486
606
  async sendStreamFrameInternal(frameData, options) {
487
607
  const effectiveVersion = options.modelVersion ?? this.modelVersion;
608
+ const di = await this.getDeviceIntelligence();
488
609
  const request = {
489
610
  session_id: options.sessionId,
490
611
  model: options.model,
491
612
  source: options.source,
492
613
  frame: frameData,
493
614
  ...options.frameCount != null ? { frame_count: options.frameCount } : {},
494
- ...options.warnings?.length ? { warnings: options.warnings } : {}
615
+ ...options.warnings?.length ? { warnings: options.warnings } : {},
616
+ ...di ? { device_intelligence: di } : {}
495
617
  };
496
618
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
497
619
  method: "POST",
@@ -2148,6 +2270,7 @@ export {
2148
2270
  OVAL_REGION_DESKTOP,
2149
2271
  OVAL_REGION_MOBILE,
2150
2272
  RETRY_CONFIG,
2273
+ SHARED_SDK_PLATFORM,
2151
2274
  TARGET_FACE_PERCENTAGE_IN_CROP,
2152
2275
  VALID_FRAME_COUNTS,
2153
2276
  analyzeBlur,
@@ -2162,6 +2285,7 @@ export {
2162
2285
  canCaptureFrame,
2163
2286
  checkEyeRegionQuality,
2164
2287
  checkFrameQuality,
2288
+ collectDeviceIntelligence,
2165
2289
  decodeBase64,
2166
2290
  detectCameraAngle,
2167
2291
  detectFaceRoll,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "3.6.1",
3
+ "version": "3.8.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",