@moveris/shared

v4.2.1

Published

Core business logic for Moveris Live SDK

Readme

@moveris/shared

Core business logic for Moveris Live SDK. This package provides the HTTP client, types, utilities, and frame management for liveness detection.

Installation

pnpm add @moveris/shared
# or
npm install @moveris/shared
# or
yarn add @moveris/shared

Quick Start

import { LivenessClient, generateSessionId } from '@moveris/shared';

// Create the client
const client = new LivenessClient({
  apiKey: 'mv_your_api_key',
  baseUrl: 'https://api.moveris.com', // optional, uses default
});

// Perform a fast liveness check
const result = await client.fastCheck(frames, {
  sessionId: 'my-session-id', // optional — auto-generated if omitted
  model: 'mixed-30-v3_1_1', // optional — defaults to DEFAULT_MODEL
  source: 'live',
});

console.log(result.verdict); // 'live', 'fake', or 'inconclusive'
console.log(result.confidence); // 0-1
console.log(result.score); // 0-100

API Reference

LivenessClient

The main client for interacting with the Moveris Liveness API.

Constructor

const client = new LivenessClient(config: LivenessClientConfig);

| Option | Type | Default | Description | | ----------------------------- | ----------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------ | | apiKey | string | required | Your Moveris API key | | baseUrl | string | 'https://api.moveris.com' | API base URL | | modelVersion | ModelVersion | - | Model version alias for X-Model-Version header (e.g. 'latest') | | timeout | number | 30000 | Request timeout in milliseconds | | enableRetry | boolean | true | Enable automatic retry with exponential backoff | | customFetch | typeof fetch | fetch | Custom fetch implementation (for React Native) | | deviceIntelligenceOverrides | DeviceIntelligenceOverrides | - | Static overrides merged into every device_intelligence payload (e.g. { vpn_detected: true }) | | consumer | ConsumerContext | - | Consumer app context — traces which app and environment submitted each transaction | | trackClientTime | boolean | false | When true, fires a fire-and-forget PATCH after each verdict to record end-to-end client duration |

Methods

fastCheck(frames, options)

Perform fast liveness check with server-side face detection. Send 30, 60 or 120 frames to match the selected model.

const result = await client.fastCheck(frames, {
  sessionId: 'optional-uuid',
  model: 'mixed-30-v3_1_1',
  source: 'live', // 'live' | 'media'
});

Parameters:

| Option | Type | Default | Description | | -------------- | ---------------- | ------------------------------------- | ----------------------------------------------------------------- | | sessionId | string | auto-generated | Unique session identifier (UUID) | | model | FastCheckModel | 'mixed-30-v3_1_1' (DEFAULT_MODEL) | Model to use (see Models section) | | modelVersion | ModelVersion | - | Version alias sent via X-Model-Version header (e.g. 'latest') | | frameCount | number | - | Frame count for alias-based model resolution | | source | FrameSource | 'live' | Frame source: 'live' (camera) or 'media' (recorded video) |

Returns: Promise<LivenessResult>

fastCheckCrops(crops, options)

Perform fast liveness check with pre-cropped 224x224 face images. Use this when you handle face detection client-side. Expects CropData[] (no timestamps — only index and pixels).

import type { CropData } from '@moveris/shared';

const crops: CropData[] = capturedFrames.map((f) => ({
  index: f.index,
  pixels: f.pixels, // base64 224x224 PNG
}));

const result = await client.fastCheckCrops(crops, {
  model: 'mixed-30-v3_1_1',
  source: 'live',
  bgSegmentation: true, // optional — sent as bg_segmentation in payload
});
streamFrame(frame, options)

Send a single frame to the streaming endpoint. Returns the API response which may include a partial or final result. Used internally by useLiveness for sequential streaming.

const response = await client.streamFrame(frame, {
  sessionId: 'uuid',
  model: 'mixed-30-v3_1_1',
  source: 'live',
  frameIndex: 0,
  totalFrames: 30,
});
verify(frames, options)

Verify liveness using spatial feature-based detection. Requires minimum 50 frames.

const result = await client.verify(frames, {
  fps: 30,
  frameWidth: 640,
  frameHeight: 480,
});
hybridCheck(frames, options)

Perform hybrid liveness check combining CNN with physiological features. Requires minimum 50 frames.

const result = await client.hybridCheck(frames, { fps: 30 });
hybrid50(frames, options)

50-frame hybrid model with 93.8% balanced accuracy.

const result = await client.hybrid50(frames, { fps: 30 });
hybrid150(frames, options)

150-frame hybrid model with 96.2% balanced accuracy. Best for high-security scenarios.

const result = await client.hybrid150(frames, { fps: 30 });
postClientTime(sessionId, clientTime)

Fire-and-forget PATCH to record end-to-end client duration for a completed verification session. No-op when trackClientTime is false. Swallows all errors silently.

// Called automatically by useLiveness / CognitoCheckWidget after the verdict.
// Approach B consumers can call it manually after onComplete resolves:
const elapsed = Math.round(performance.now() - startTime);
void client.postClientTime(result.sessionId, elapsed);
updateDeviceIntelligenceOverrides(overrides)

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.

client.updateDeviceIntelligenceOverrides({
  camera: { device_name: 'FaceTime HD Camera', resolution: '1280x720' },
});
client.updateDeviceIntelligenceOverrides({ vpn_detected: true });
health()

Check API health status.

const health = await client.health();
console.log(health.status); // 'ok'
console.log(health.models_loaded); // ['mixed-30-v3_1_1', 'mixed-60-v3_1_1', 'mixed-120-v3_1_1']
getJobResult(jobId)

Get job status for async processing.

const job = await client.getJobResult('job-uuid');
console.log(job.status); // 'queued' | 'processing' | 'complete' | 'failed'
waitForJobResult(jobId, timeout)

Long-poll for job completion (max 120 seconds).

const job = await client.waitForJobResult('job-uuid', 30);

Types

FastCheckModel

Model selection for liveness detection speed/accuracy trade-off.

type FastCheckModel =
  | 'mixed-30-v3_1_1'
  | 'mixed-60-v3_1_1'
  | 'mixed-120-v3_1_1'
  | 'clock-1s-v3_2'
  | 'clock-1s-v4_2'
  | 'clock-1s-ensemble-v3_2-v4_2'
  | (string & object);

Mixed V3.1.1 models (aliases: 'latest', 'v3_1_1'):

| Model | Frames | Description | | -------------------- | ------ | ------------------------------------- | | 'mixed-30-v3_1_1' | 30 | Balanced speed and accuracy (default) | | 'mixed-60-v3_1_1' | 60 | Higher accuracy | | 'mixed-120-v3_1_1' | 120 | Highest accuracy, longest capture |

Clock-time models score the first 1 s of capture instead of a frame count. They only run on fast-check with full frames; client.fastCheck() drops frames whose timestamp does not advance, rebases timestamps to the first frame, refuses a capture shorter than 1 s with insufficient_duration, and sends real capture timestamps without frame_count or the X-Model-Version header:

| Model | Capture | Description | | ------------------------------- | ------- | -------------------------------------------------------- | | 'clock-1s-v3_2' | 1 s | Clock V3.2, at most 120 frames inside the window | | 'clock-1s-ensemble-v3_2-v4_2' | 1 s | Ensemble of Clock V3.2 and V4.2 | | 'clock-1s-v4_2' | 1 s | In MODEL_CONFIGS but not listed by getActiveModels() |

clock-1s-v4_2 is not offered on its own because it accepts too many fakes below about 25 fps. Every other client method (fastCheckCrops, streamFrame, fastCheckStream, fastCheckStreamSequential, liveCheck and the v2 methods) throws model_endpoint_not_supported before sending anything. When the model cannot score a capture, the result has verdict 'inconclusive' and the reason in unscorableReason.

When model is omitted, every LivenessClient method uses DEFAULT_MODEL ('mixed-30-v3_1_1'). For the live model registry served by the API, call client.getModels() (GET /api/v1/models).

ConsumerContext

Consumer app context attached to every verification request for transaction tracing.

interface ConsumerContext {
  url: string; // The consumer app's URL
  env: 'development' | 'staging' | 'production'; // Consumer app environment
}

Pass it at client construction — it is included in every subsequent request automatically:

const client = new LivenessClient({
  apiKey: 'mv_your_api_key',
  consumer: { url: 'https://app.example.com', env: 'production' },
  trackClientTime: true, // optional — fires postClientTime after each verdict
});

FrameSource

Source of the captured frames.

type FrameSource = 'media' | 'live';

| Value | Description | | --------- | ----------------------- | | 'live' | Real-time camera feed | | 'media' | Pre-recorded video file |

Verdict

Liveness determination result.

type Verdict = 'live' | 'fake' | 'inconclusive';

| Value | Description | | ---------------- | -------------------------------------------------------------------------------- | | 'live' | Real person detected | | 'fake' | Spoofing attempt detected (photo, video, mask, etc.) | | 'inconclusive' | API processed successfully but could not determine a verdict — retry recommended |

LivenessState

State machine for liveness detection flow.

type LivenessState =
  | 'idle' // Ready to start
  | 'capturing' // Capturing frames from camera
  | 'uploading' // Uploading frames to API
  | 'processing' // API is processing frames
  | 'complete' // Verification complete
  | 'error'; // Error occurred

LivenessResult

Result returned from liveness verification.

interface LivenessResult {
  verdict: Verdict; // 'live', 'fake', or 'inconclusive'
  confidence: number; // Confidence score (0-1)
  score: number; // Liveness score (0-100)
  sessionId: string; // Session identifier
  processingMs: number; // Server processing time
  framesProcessed: number; // Number of frames analyzed
  deprecation?: DeprecationInfo; // Present when X-Moveris-Model-Resolved header is returned
  clientTime?: number; // End-to-end client duration in ms (start() to verdict received)
}

CapturedFrame

Individual frame captured from camera.

interface CapturedFrame {
  index: number; // Frame sequence number (0-based)
  timestampMs: number; // Timestamp from capture start
  pixels: string; // Base64-encoded image data
}

CropData

Pre-cropped face image for the fast-check-crops endpoint. Omits timestampMs since timing is not needed for cropped submissions.

interface CropData {
  index: number; // Frame sequence number (0-based)
  pixels: string; // Base64-encoded 224x224 PNG image
}

ModelVersion

Version alias sent via the X-Model-Version request header for server-side model resolution.

type ModelVersion = 'latest' | 'v3_1_1' | (string & object);

| Value | Resolves to | | ---------- | ----------------------------- | | 'latest' | Mixed V3.1.1 family (current) | | 'v3_1_1' | Mixed V3.1.1 family (pinned) |

When X-Model-Version is set, the API also requires frame_count in the request body. Pass it via the frameCount option.

DeprecationInfo

Parsed from API response headers when the server resolves a model.

interface DeprecationInfo {
  deprecated: boolean; // true when model is deprecated
  resolvedModel: string; // Concrete model id (X-Moveris-Model-Resolved)
  deprecatedModel?: string; // Deprecated model id (X-Moveris-Deprecated-Model)
  sunsetDate?: string; // ISO date of removal (Sunset header)
  suggestedModel?: string; // Replacement model (X-Moveris-Suggested-Model)
}

Model Versioning & Deprecation

The client supports model alias resolution via the X-Model-Version header and automatically parses deprecation headers from API responses.

// Use aliases for automatic server-side resolution
const client = new LivenessClient({
  apiKey: 'mv_your_api_key',
  modelVersion: 'latest', // sent as X-Model-Version header on every request
});

const result = await client.fastCheck(frames, {
  model: 'mixed-30-v3_1_1',
  frameCount: 30, // required when modelVersion is set
});

// Deprecation info is automatically parsed from response headers
if (result.deprecation?.deprecated) {
  console.warn(`Model "${result.deprecation.resolvedModel}" is deprecated.`);
  console.warn(`Migrate to "${result.deprecation.suggestedModel}".`);
  console.warn(`Sunset date: ${result.deprecation.sunsetDate}`);
}

// Per-call override (takes precedence over client-level modelVersion)
const result2 = await client.fastCheck(frames, {
  modelVersion: 'v3_1_1',
  frameCount: 60,
});

Valid Frame Counts

When using model version aliases, frameCount must be one of:

import { VALID_FRAME_COUNTS } from '@moveris/shared';
// [30, 60, 120]

Utilities

generateSessionId()

Generate a UUID v4 for session identification.

import { generateSessionId } from '@moveris/shared';

const sessionId = generateSessionId();
// e.g., 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d'

Session ID injection: Every LivenessClient method (fastCheck, fastCheckCrops, streamFrame, verify, hybrid50, hybrid150) accepts an optional sessionId in its options object. If provided, that ID is used for the request; if omitted, a new UUID is generated automatically. This is useful for debugging, testing, or correlating client requests with server logs. In @moveris/react, the sessionId can be injected via the useLiveness hook config or the LivenessView / LivenessModal props.

toFrameData(frames)

Convert CapturedFrame array to API format.

import { toFrameData } from '@moveris/shared';

const apiFrames = toFrameData(capturedFrames);

toHybridFrameData(frames)

Convert frames to hybrid endpoint format.

import { toHybridFrameData } from '@moveris/shared';

const hybridFrames = toHybridFrameData(capturedFrames);

Frame Buffer Management

FrameBuffer

Manages a sliding window buffer of frames with automatic cleanup.

import { FrameBuffer } from '@moveris/shared';

const buffer = new FrameBuffer({
  maxSize: 10, // Maximum frames to keep
  maxMemoryBytes: 4 * 1024 * 1024, // 4MB limit
});

// Add frames
buffer.add(frame);

// Get all frames
const frames = buffer.getAll();

// Acknowledge processed frames
buffer.acknowledge(5); // Remove frames up to index 5

// Clear buffer
buffer.clear();

FrameQueue

FIFO queue for frame processing.

import { FrameQueue } from '@moveris/shared';

const queue = new FrameQueue({ maxSize: 50 });

queue.enqueue(frame);
const frame = queue.dequeue();
const peeked = queue.peek();

Error Handling

LivenessApiError

Custom error class for API errors.

import { LivenessApiError } from '@moveris/shared';

try {
  await client.fastCheck(frames);
} catch (error) {
  if (error instanceof LivenessApiError) {
    console.log(error.code); // 'insufficient_frames'
    console.log(error.message); // 'Not enough frames provided'
    console.log(error.statusCode); // 400
    console.log(error.required); // 10
    console.log(error.received); // 5
  }
}

Error Codes:

| Code | Description | | --------------------- | -------------------------- | | insufficient_frames | Not enough frames provided | | invalid_model | Invalid model specified | | invalid_key | Invalid API key | | missing_field | Required field missing | | timeout | Request timeout | | network_error | Network connectivity issue |


Feedback Messages

Localized user feedback for liveness detection UI.

import { getFeedbackMessage, getStatusMessage, DEFAULT_LOCALE, ES_LOCALE } from '@moveris/shared';

// Get localized feedback
const message = getFeedbackMessage('face_not_centered', ES_LOCALE);
// "Centra tu rostro en el óvalo"

// Get status message
const status = getStatusMessage('capturing', DEFAULT_LOCALE);
// "Hold still..."

Feedback Keys

| Key | English | Description | | ------------------- | -------------------------------- | --------------------------------------------------------------------------------- | | no_face | "No face detected" | Face detection failed | | face_not_centered | "Center your face in the circle" | Face outside guide | | too_close | "Move back a little" | Face too close to camera | | too_far | "Move closer" | Face too far from camera | | poor_lighting | "Improve lighting" | Insufficient light | | hold_still | "Hold still" | Movement detected | | capturing | "Capturing..." | Frame capture in progress | | processing | "Processing..." | API verification in progress | | success | "Verification complete" | Successful completion | | failed | "Verification failed" | Failed verification | | eyes_not_visible | "Eyes not clearly visible" | Eye region featureless | | eyes_shadowed | "Eyes are in shadow…" | Eye region too dark | | eyes_overexposed | "Eye region overexposed…" | Eye region too bright | | glasses_glare | "Glare detected…" | Specular highlights on eyes | | eye_quality_poor | "Eye region quality is poor" | Generic eye quality failure | | camera_angle_low | "Raise camera to eye level" | Camera below face (Y < 0.3) | | camera_angle_high | "Lower camera to eye level" | Camera above face (Y > 0.7) | | camera_tilted | "Hold camera level" | Device tilt > 15° (via transformation matrix, falls back to eye-corner landmarks) | | flat_lighting | "Find better lighting" | Face-region dynamic range < 60 L-units — soft warning, capture continues |


Oval Guide States

Visual feedback states for the face positioning guide.

import { getOvalGuideState, OVAL_GUIDE_COLORS } from '@moveris/shared';

const state = getOvalGuideState(alignmentScore);
// Returns: 'no_face' | 'poor' | 'good' | 'perfect'

const color = OVAL_GUIDE_COLORS[state];
// 'red' | 'orange' | 'yellow' | 'green'

| State | Color | Alignment Score | | --------- | ------ | ---------------- | | no_face | Red | No face detected | | poor | Orange | < 0.5 | | good | Yellow | 0.5 - 0.8 | | perfect | Green | > 0.8 |


Eye Region Quality Analysis

Pre-request eye-region quality gate. Platform-agnostic functions that analyze RGBA pixel data from eye regions to detect shadows, glare, occlusion, and poor visibility.

import {
  checkEyeRegionQuality,
  analyzeEyeRegionBrightness,
  analyzeEyeRegionContrast,
  detectSpecularHighlights,
  EYE_QUALITY_THRESHOLDS,
} from '@moveris/shared';

// Combined quality check (recommended)
const quality = checkEyeRegionQuality(eyeRegionPixels);
console.log(quality.passed); // boolean
console.log(quality.brightness); // 0-255
console.log(quality.contrast); // standard deviation of luminance
console.log(quality.hasGlare); // boolean
console.log(quality.glareRatio); // 0-1
console.log(quality.message); // user-facing feedback or null

// Individual checks
const brightness = analyzeEyeRegionBrightness(pixels);
const contrast = analyzeEyeRegionContrast(pixels);
const glareRatio = detectSpecularHighlights(pixels);

| Check | Threshold | Condition | | ----------- | --------------------- | --------------------------------------- | | Shadowed | minBrightness: 40 | Average luminance too low | | Overexposed | maxBrightness: 230 | Average luminance too high | | Glare | maxGlareRatio: 0.15 | >15% of pixels are specular highlights | | Occluded | minContrast: 12 | Standard deviation of luminance too low |

Glare detection uses a relative threshold (mean brightness × 2.5) so sensitivity adapts to ambient lighting, avoiding false positives in bright environments.

Custom thresholds can be passed as a second argument to checkEyeRegionQuality().

Eye Region Landmarks

Extract bounding boxes for left and right eye regions from MediaPipe 468-point face mesh landmarks.

import { getEyeRegionBounds, EYE_LANDMARK_INDICES, validateFaceLandmarks } from '@moveris/shared';

const bounds = getEyeRegionBounds(landmarks);
if (bounds) {
  console.log(bounds.leftEye); // { x, y, width, height } (normalized 0-1)
  console.log(bounds.rightEye); // { x, y, width, height } (normalized 0-1)
}

Frame Analysis Utilities

Utilities for assessing frame quality before submission.

import {
  isFaceFullyVisible,
  isFaceInOval,
  calculateFaceCropRegion,
  detectFaceRoll,
  detectFaceRollFromMatrix,
  detectCameraAngle,
} from '@moveris/shared';

// Check if face is fully visible in frame
const visibility = isFaceFullyVisible(faceBbox, frameWidth, frameHeight);
console.log(visibility.isVisible); // true/false
console.log(visibility.reason); // 'left_edge' | 'top_edge' | etc.

// Check if face is within the oval guide
const inOval = isFaceInOval(faceBbox, ovalRegion);
console.log(inOval.isInOval); // true/false
console.log(inOval.alignmentScore); // 0-1

// Calculate crop region for face — frame-relative 20% margin on each side,
// matches the API server-side crop and the model training pipeline.
const cropRegion = calculateFaceCropRegion(faceBbox, frameWidth, frameHeight);
// { x, y, width, height } — possibly non-square rectangle in source pixel
// coordinates. Callers must resize the cropped pixels to 224×224 with
// bilinear interpolation; non-square crops are squashed (intentional, matches
// cv2.resize behaviour applied server-side).

// Detect face roll (device tilt) — prefer matrix when available (more accurate under glasses/occlusion)
if (facialTransformationMatrix) {
  const rollResult = detectFaceRollFromMatrix(facialTransformationMatrix); // 16-element column-major 4×4
  // { roll: number (degrees), tooTilted: boolean }
} else {
  const rollResult = detectFaceRoll(landmarks); // fallback: eye-corner landmarks, requires ≥364 points
  // { roll: number (degrees), tooTilted: boolean }
}

// Detect camera vertical angle from forehead/nose/chin landmark ratio
const angleResult = detectCameraAngle(landmarks); // requires ≥153 landmarks
// { ratio: number, cameraAbove: boolean, cameraBelow: boolean }
// ratio ~1.0 at eye level; >1.35 = camera above user; <0.75 = camera below user

// Analyse face-region dynamic range (flat lighting detection, MOV-1280)
// Pass pre-sampled face-region pixels — use sampleFaceRegionPixels() in @moveris/react
import { analyzeDynamicRange } from '@moveris/shared';
const drResult = analyzeDynamicRange(rgbaPixels);
// { range: number (max L* − min L*), isLow: boolean (< 60) }
// Soft warning — do not use as a hard capture gate

Crop Constants (aligned with the model contract)

These constants control how face crops are generated for the fast-check-crops endpoint. They match the algorithm applied server-side in m-ai-check-api and during model training:

  1. Detect the face. Get the bounding box in source pixel coordinates.
  2. Expand the bbox by FACE_CROP_FRAME_MARGIN × frameWidth/Height on each side.
  3. Clamp the resulting rectangle to [0, frameWidth] × [0, frameHeight]. The crop is not necessarily square.
  4. Resize the cropped pixels to exactly FACE_CROP_OUTPUT_SIZE × FACE_CROP_OUTPUT_SIZE with bilinear interpolation. Non-square crops are squashed (matches cv2.resize server-side).

| Constant | Value | Description | | -------------------------------- | ------ | --------------------------------------------------------------------- | | FACE_CROP_FRAME_MARGIN | 0.2 | Margin around the face bbox, as a fraction of frame W/H, on each side | | FACE_CROP_OUTPUT_SIZE | 224 | Required output size for the cropped face image (px) | | MAX_FACE_PERCENTAGE_IN_CROP | 0.45 | UX guard — nudge users back when the face dominates the crop | | TARGET_FACE_PERCENTAGE_IN_CROP | 0.33 | UX-only target framing; not a model contract |


Model Configuration

import {
  DEFAULT_MODEL,
  MODEL_CONFIGS,
  VALID_FRAME_COUNTS,
  isDeprecatedModel,
  getActiveModels,
  getMinFramesForModel,
  isClockTimeModel,
  getCaptureRequirement,
  ClockCaptureTracker,
} from '@moveris/shared';

// Default model used when `model` is omitted
console.log(DEFAULT_MODEL); // 'mixed-30-v3_1_1'

// Access model metadata
const config = MODEL_CONFIGS['mixed-60-v3_1_1'];
console.log(config.minFrames); // 60
console.log(config.deprecated); // false
console.log(config.aliases); // ['latest', 'v3_1_1']

// Check if a model is deprecated (runtime deprecation arrives via result.deprecation)
isDeprecatedModel('mixed-30-v3_1_1'); // false

// Get only active (non-deprecated, exposed) models
const active = getActiveModels();
// ['mixed-30-v3_1_1', 'mixed-60-v3_1_1', 'mixed-120-v3_1_1', 'clock-1s-v3_2', 'clock-1s-ensemble-v3_2-v4_2']

// Unknown model ids fall back to the default frame budget
getMinFramesForModel('some-future-model'); // 30

// Clock-time models are captured by time; their frame count is only a cap
isClockTimeModel('clock-1s-v3_2'); // true
getCaptureRequirement('clock-1s-v3_2');
// { kind: 'duration', durationMs: 1000, targetMs: 1100, maxFrames: 120 }
getCaptureRequirement('clock-2s-v5'); // id outside MODEL_CONFIGS: durationMs 2000, targetMs 2200
getCaptureRequirement('mixed-30-v3_1_1'); // { kind: 'frames', frames: 30 }
getMinFramesForModel('clock-1s-v3_2'); // 2 (the API minimum; the frame cap is getCaptureRequirement().maxFrames)

// Track a clock-time capture in a custom hook
// Sized from the model: completes at its target coverage or frame cap; null for frame-count models
const tracker = ClockCaptureTracker.forModel('clock-1s-v3_2');
tracker?.add(performance.now() - captureStart); // false for a timestamp that does not advance
tracker?.coveredMs; // (last - first) + median frame gap, as the API measures it
tracker?.isComplete;

VALID_FRAME_COUNTS; // [30, 60, 120]

MODEL_CONFIGS is the compile-time fallback. For the live registry, call client.getModels() (GET /api/v1/models).


Device Intelligence

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.

collectDeviceIntelligence(opts?)

Collect device metadata manually. Useful for Approach B consumers (custom UI + raw fetch()) who bypass LivenessClient.

import { collectDeviceIntelligence } from '@moveris/shared';

const di = await collectDeviceIntelligence({
  platformVersion: '[email protected]', // optional — defaults to SHARED_SDK_PLATFORM
  overrides: { vpn_detected: true }, // optional — merged on top of collected data
});

if (di) {
  console.log(di.ip); // '1.2.3.4'
  console.log(di.country); // 'US'
  console.log(di.isp); // 'Comcast Cable'
  console.log(di.platform_version); // '[email protected]'
}
// Returns null if ipinfo.io is unreachable — handle gracefully

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 [email protected][email protected] string is used automatically.

SHARED_SDK_PLATFORM

Pre-formatted platform string for the platform_version field. Reads the version from package.json at build time — never goes stale.

import { SHARED_SDK_PLATFORM } from '@moveris/shared';

console.log(SHARED_SDK_PLATFORM); // '[email protected]'

Device Intelligence Types

import type {
  DeviceIntelligence,
  DeviceIntelligenceOverrides,
  DeviceIntelligenceGeo,
  DeviceIntelligenceCamera,
} from '@moveris/shared';
interface DeviceIntelligence {
  ip: string;
  country: string; // ISO 3166-1 alpha-2, e.g. 'US'
  region: string; // e.g. 'California'
  city: string; // e.g. 'San Francisco'
  isp?: string; // Internet service provider
  user_agent?: string; // navigator.userAgent
  platform_version?: string; // SDK version string
  vpn_detected?: boolean;
  camera?: DeviceIntelligenceCamera;
}

interface DeviceIntelligenceCamera {
  device_name?: string; // e.g. 'FaceTime HD Camera'
  resolution?: string; // e.g. '1280x720'
}

interface DeviceIntelligenceOverrides {
  vpn_detected?: boolean;
  camera?: DeviceIntelligenceCamera;
  [key: string]: unknown;
}

CSP Note

If your app enforces a Content Security Policy, add https://ipinfo.io to connect-src:

connect-src 'self' https://api.moveris.com https://ipinfo.io;

Without this, the ipinfo.io fetch will be blocked silently and device_intelligence will be omitted from all requests.


Configuration Constants

import { DEFAULT_ENDPOINT, AUTH_CONFIG, RETRY_CONFIG, FRAME_CONFIG } from '@moveris/shared';

console.log(DEFAULT_ENDPOINT); // 'https://api.moveris.com'
console.log(AUTH_CONFIG.apiKeyHeader); // 'X-API-Key'
console.log(RETRY_CONFIG.maxAttempts); // 3
console.log(FRAME_CONFIG.maxBufferSize); // 10

TypeScript Support

This package is written in TypeScript and provides full type definitions.

import type {
  LivenessResult,
  LivenessState,
  LivenessConfig,
  FastCheckModel,
  ModelVersion,
  DeprecationInfo,
  ModelConfig,
  FrameSource,
  Verdict,
  CapturedFrame,
  CropData,
  ConsumerContext,
  LivenessClientConfig,
  DetectionResult,
  DetectionSummary,
  FaceBoundingBox,
  HeadPose,
  FaceLandmarkPoint,
  LandmarkValidationResult,
  EyeRegionQuality,
  EyeQualityThresholds,
  EyeRegionBounds,
  EyeRegionsBounds,
  // Device Intelligence Types
  DeviceIntelligence,
  DeviceIntelligenceOverrides,
  DeviceIntelligenceGeo,
  DeviceIntelligenceCamera,
} from '@moveris/shared';

License

MIT