@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 +118 -15
- package/dist/index.d.mts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +135 -9
- package/dist/index.mjs +133 -9
- package/package.json +1 -1
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 '
|
|
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
|
|
51
|
-
|
|
|
52
|
-
| `apiKey`
|
|
53
|
-
| `baseUrl`
|
|
54
|
-
| `modelVersion`
|
|
55
|
-
| `timeout`
|
|
56
|
-
| `enableRetry`
|
|
57
|
-
| `customFetch`
|
|
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
|
|
267
|
-
|
|
|
268
|
-
| `'live'`
|
|
269
|
-
| `'fake'`
|
|
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 '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
286
|
-
|
|
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
|
|
469
|
-
if (!
|
|
470
|
-
return { [AUTH_CONFIG.modelVersionHeader]:
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
|
325
|
-
if (!
|
|
326
|
-
return { [AUTH_CONFIG.modelVersionHeader]:
|
|
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,
|