@moveris/shared 2.9.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -35,7 +35,8 @@ var FRAME_BUFFER_CONFIG = {
35
35
  var AUTH_CONFIG = {
36
36
  timeout: 3e4,
37
37
  // 30 seconds for API requests
38
- apiKeyHeader: "X-API-Key"
38
+ apiKeyHeader: "X-API-Key",
39
+ modelVersionHeader: "X-Model-Version"
39
40
  };
40
41
  var FRAME_CONFIG = {
41
42
  targetFPS: 30,
@@ -164,14 +165,22 @@ var LivenessClient = class _LivenessClient {
164
165
  constructor(config) {
165
166
  this.baseUrl = (config.baseUrl ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
166
167
  this.apiKey = config.apiKey;
168
+ this.modelVersion = config.modelVersion;
167
169
  this.timeout = config.timeout ?? AUTH_CONFIG.timeout;
168
170
  this.enableRetry = config.enableRetry ?? true;
169
171
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
170
172
  }
171
173
  /**
172
- * Make an authenticated API request
174
+ * Make an authenticated API request, returning the parsed body.
173
175
  */
174
176
  async request(path, options = {}) {
177
+ const { data } = await this.requestRaw(path, options);
178
+ return data;
179
+ }
180
+ /**
181
+ * Make an authenticated API request, returning both parsed body and headers.
182
+ */
183
+ async requestRaw(path, options = {}) {
175
184
  const url = `${this.baseUrl}${path}`;
176
185
  const controller = new AbortController();
177
186
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@@ -189,7 +198,8 @@ var LivenessClient = class _LivenessClient {
189
198
  if (!response.ok) {
190
199
  throw await this.parseErrorResponse(response);
191
200
  }
192
- return await response.json();
201
+ const data = await response.json();
202
+ return { data, headers: response.headers };
193
203
  } catch (error) {
194
204
  clearTimeout(timeoutId);
195
205
  if (error instanceof LivenessApiError) {
@@ -274,6 +284,30 @@ var LivenessClient = class _LivenessClient {
274
284
  const match = /'error'\s*:\s*'([^']+)'/.exec(text) ?? /"error"\s*:\s*"([^"]+)"/.exec(text);
275
285
  return match?.[1] ?? null;
276
286
  }
287
+ /**
288
+ * Parse deprecation-related headers from an API response.
289
+ * Returns undefined when no deprecation info is present.
290
+ */
291
+ static parseDeprecationHeaders(headers) {
292
+ const resolved = headers.get("x-moveris-model-resolved");
293
+ if (!resolved) return void 0;
294
+ const deprecated = headers.get("deprecation") === "true";
295
+ return {
296
+ deprecated,
297
+ resolvedModel: resolved,
298
+ deprecatedModel: headers.get("x-moveris-deprecated-model") ?? void 0,
299
+ sunsetDate: headers.get("sunset") ?? void 0,
300
+ suggestedModel: headers.get("x-moveris-suggested-model") ?? void 0
301
+ };
302
+ }
303
+ /**
304
+ * Build extra request headers for model versioning.
305
+ */
306
+ buildModelVersionHeaders(modelVersion) {
307
+ const version = modelVersion ?? this.modelVersion;
308
+ if (!version) return {};
309
+ return { [AUTH_CONFIG.modelVersionHeader]: version };
310
+ }
277
311
  /**
278
312
  * Make a request with optional retry
279
313
  */
@@ -288,6 +322,20 @@ var LivenessClient = class _LivenessClient {
288
322
  }
289
323
  return this.request(path, options);
290
324
  }
325
+ /**
326
+ * Make a request with optional retry, returning both data and headers.
327
+ */
328
+ async requestWithRetryRaw(path, options = {}) {
329
+ if (this.enableRetry) {
330
+ return retryWithBackoff(() => this.requestRaw(path, options), {
331
+ maxAttempts: RETRY_CONFIG.maxAttempts,
332
+ initialDelay: RETRY_CONFIG.initialDelay,
333
+ maxDelay: RETRY_CONFIG.maxDelay,
334
+ backoffMultiplier: RETRY_CONFIG.backoffMultiplier
335
+ });
336
+ }
337
+ return this.requestRaw(path, options);
338
+ }
291
339
  // ===========================================================================
292
340
  // Health & Status
293
341
  // ===========================================================================
@@ -322,18 +370,31 @@ var LivenessClient = class _LivenessClient {
322
370
  * @returns Liveness result
323
371
  */
324
372
  async fastCheck(frames, options = {}) {
373
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
325
374
  const request = {
326
375
  session_id: options.sessionId ?? generateSessionId(),
327
376
  model: options.model ?? "10",
328
377
  source: options.source ?? "live",
329
378
  frames: toFrameData(frames),
379
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
330
380
  ...options.warnings?.length ? { warnings: options.warnings } : {}
331
381
  };
332
- const response = await this.requestWithRetry(API_PATHS.fastCheck, {
333
- method: "POST",
334
- body: JSON.stringify(request)
335
- });
336
- return toLivenessResult(response);
382
+ const { data: response, headers } = await this.requestWithRetryRaw(
383
+ API_PATHS.fastCheck,
384
+ {
385
+ method: "POST",
386
+ body: JSON.stringify(request),
387
+ headers: this.buildModelVersionHeaders(effectiveVersion)
388
+ }
389
+ );
390
+ const result = toLivenessResult(response);
391
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
392
+ if (result.deprecation?.deprecated) {
393
+ console.warn(
394
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
395
+ );
396
+ }
397
+ return result;
337
398
  }
338
399
  /**
339
400
  * Perform fast liveness check with pre-cropped face images
@@ -343,19 +404,32 @@ var LivenessClient = class _LivenessClient {
343
404
  * @returns Liveness result
344
405
  */
345
406
  async fastCheckCrops(crops, options = {}) {
407
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
346
408
  const request = {
347
409
  session_id: options.sessionId ?? generateSessionId(),
348
410
  model: options.model ?? "10",
349
411
  source: options.source ?? "live",
350
412
  crops,
413
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
351
414
  ...options.warnings?.length ? { warnings: options.warnings } : {},
352
415
  ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
353
416
  };
354
- const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
355
- method: "POST",
356
- body: JSON.stringify(request)
357
- });
358
- return toLivenessResult(response);
417
+ const { data: response, headers } = await this.requestWithRetryRaw(
418
+ API_PATHS.fastCheckCrops,
419
+ {
420
+ method: "POST",
421
+ body: JSON.stringify(request),
422
+ headers: this.buildModelVersionHeaders(effectiveVersion)
423
+ }
424
+ );
425
+ const result = toLivenessResult(response);
426
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
427
+ if (result.deprecation?.deprecated) {
428
+ console.warn(
429
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
430
+ );
431
+ }
432
+ return result;
359
433
  }
360
434
  /**
361
435
  * Send a single captured frame to the streaming endpoint.
@@ -383,28 +457,29 @@ var LivenessClient = class _LivenessClient {
383
457
  return this.sendStreamFrameInternal(frameData, {
384
458
  sessionId: options.sessionId,
385
459
  model: options.model ?? "10",
460
+ modelVersion: options.modelVersion,
461
+ frameCount: options.frameCount,
386
462
  source: options.source ?? "live",
387
463
  warnings: options.warnings
388
464
  });
389
465
  }
390
466
  /**
391
467
  * Send a single FrameData to the streaming endpoint with retry (internal)
392
- *
393
- * @param frame - Single frame to send
394
- * @param options - Session and model options
395
- * @returns Stream response with status
396
468
  */
397
469
  async sendStreamFrameInternal(frameData, options) {
470
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
398
471
  const request = {
399
472
  session_id: options.sessionId,
400
473
  model: options.model,
401
474
  source: options.source,
402
475
  frame: frameData,
476
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
403
477
  ...options.warnings?.length ? { warnings: options.warnings } : {}
404
478
  };
405
479
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
406
480
  method: "POST",
407
- body: JSON.stringify(request)
481
+ body: JSON.stringify(request),
482
+ headers: this.buildModelVersionHeaders(effectiveVersion)
408
483
  });
409
484
  }
410
485
  /**
@@ -429,6 +504,8 @@ var LivenessClient = class _LivenessClient {
429
504
  const response = await this.sendStreamFrameInternal(frameData, {
430
505
  sessionId,
431
506
  model,
507
+ modelVersion: options.modelVersion,
508
+ frameCount: options.frameCount,
432
509
  source,
433
510
  warnings: options.warnings
434
511
  });
@@ -479,6 +556,8 @@ var LivenessClient = class _LivenessClient {
479
556
  const response = await this.sendStreamFrameInternal(frameData, {
480
557
  sessionId,
481
558
  model,
559
+ modelVersion: options.modelVersion,
560
+ frameCount: options.frameCount,
482
561
  source,
483
562
  warnings: options.warnings
484
563
  });
@@ -755,6 +834,7 @@ var FrameQueue = class {
755
834
  };
756
835
 
757
836
  // src/types/models.ts
837
+ var VALID_FRAME_COUNTS = [10, 30, 60, 90, 120];
758
838
  var MODEL_CONFIGS = {
759
839
  // Standard fast-check models
760
840
  "10": {
@@ -762,21 +842,24 @@ var MODEL_CONFIGS = {
762
842
  minFrames: 10,
763
843
  recommendedFrames: 10,
764
844
  description: "Fast model - 10 frames, quick verification",
765
- deprecated: false
845
+ deprecated: false,
846
+ aliases: ["fast"]
766
847
  },
767
848
  "50": {
768
849
  type: "50",
769
850
  minFrames: 50,
770
851
  recommendedFrames: 50,
771
852
  description: "Balanced model - 50 frames, good accuracy",
772
- deprecated: false
853
+ deprecated: false,
854
+ aliases: ["spatial"]
773
855
  },
774
856
  "250": {
775
857
  type: "250",
776
858
  minFrames: 250,
777
859
  recommendedFrames: 250,
778
860
  description: "High-accuracy model - 250 frames, best accuracy",
779
- deprecated: false
861
+ deprecated: false,
862
+ aliases: ["spatial"]
780
863
  },
781
864
  // Hybrid V2 models with physiological features
782
865
  "hybrid-v2-10": {
@@ -784,63 +867,72 @@ var MODEL_CONFIGS = {
784
867
  minFrames: 10,
785
868
  recommendedFrames: 10,
786
869
  description: "Hybrid V2 10-frame model with physio features",
787
- deprecated: false
870
+ deprecated: false,
871
+ aliases: ["hybrid"]
788
872
  },
789
873
  "hybrid-v2-30": {
790
874
  type: "hybrid-v2-30",
791
875
  minFrames: 30,
792
876
  recommendedFrames: 30,
793
877
  description: "Hybrid V2 30-frame model with physio features",
794
- deprecated: false
878
+ deprecated: false,
879
+ aliases: ["hybrid"]
795
880
  },
796
881
  "hybrid-v2-50": {
797
882
  type: "hybrid-v2-50",
798
883
  minFrames: 50,
799
884
  recommendedFrames: 50,
800
885
  description: "Hybrid V2 50-frame model with physio features",
801
- deprecated: false
886
+ deprecated: false,
887
+ aliases: ["hybrid"]
802
888
  },
803
889
  "hybrid-v2-60": {
804
890
  type: "hybrid-v2-60",
805
891
  minFrames: 60,
806
892
  recommendedFrames: 60,
807
893
  description: "Hybrid V2 60-frame model with physio features",
808
- deprecated: false
894
+ deprecated: false,
895
+ aliases: ["hybrid"]
809
896
  },
810
897
  "hybrid-v2-90": {
811
898
  type: "hybrid-v2-90",
812
899
  minFrames: 90,
813
900
  recommendedFrames: 90,
814
901
  description: "Hybrid V2 90-frame model with physio features",
815
- deprecated: false
902
+ deprecated: false,
903
+ aliases: ["hybrid"]
816
904
  },
817
905
  "hybrid-v2-100": {
818
906
  type: "hybrid-v2-100",
819
907
  minFrames: 100,
820
908
  recommendedFrames: 100,
821
909
  description: "Hybrid V2 100-frame model with physio features",
822
- deprecated: false
910
+ deprecated: false,
911
+ aliases: ["hybrid"]
823
912
  },
824
913
  "hybrid-v2-125": {
825
914
  type: "hybrid-v2-125",
826
915
  minFrames: 125,
827
916
  recommendedFrames: 125,
828
917
  description: "Hybrid V2 125-frame model with physio features",
829
- deprecated: false
918
+ deprecated: false,
919
+ aliases: ["hybrid"]
830
920
  },
831
921
  "hybrid-v2-150": {
832
922
  type: "hybrid-v2-150",
833
923
  minFrames: 150,
834
924
  recommendedFrames: 150,
835
925
  description: "Hybrid V2 150-frame model with physio features",
836
- deprecated: false
926
+ deprecated: false,
927
+ aliases: ["hybrid"]
837
928
  },
838
929
  "hybrid-v2-250": {
839
930
  type: "hybrid-v2-250",
840
931
  minFrames: 250,
841
932
  recommendedFrames: 250,
842
933
  description: "Hybrid V2 250-frame model with physio features",
843
- deprecated: false
934
+ deprecated: false,
935
+ aliases: ["hybrid"]
844
936
  },
845
937
  // Mixed V1 models — deprecated, use mixed-*-v2 equivalents instead
846
938
  "mixed-10": {
@@ -848,49 +940,68 @@ var MODEL_CONFIGS = {
848
940
  minFrames: 10,
849
941
  recommendedFrames: 10,
850
942
  description: "Mixed 10-frame model",
851
- deprecated: true
943
+ deprecated: true,
944
+ aliases: ["v1"],
945
+ sunsetDate: "2026-09-01",
946
+ replacement: "mixed-10-v2"
852
947
  },
853
948
  "mixed-30": {
854
949
  type: "mixed-30",
855
950
  minFrames: 30,
856
951
  recommendedFrames: 30,
857
952
  description: "Mixed 30-frame model",
858
- deprecated: true
953
+ deprecated: true,
954
+ aliases: ["v1"],
955
+ sunsetDate: "2026-09-01",
956
+ replacement: "mixed-30-v2"
859
957
  },
860
958
  "mixed-60": {
861
959
  type: "mixed-60",
862
960
  minFrames: 60,
863
961
  recommendedFrames: 60,
864
962
  description: "Mixed 60-frame model",
865
- deprecated: true
963
+ deprecated: true,
964
+ aliases: ["v1"],
965
+ sunsetDate: "2026-09-01",
966
+ replacement: "mixed-60-v2"
866
967
  },
867
968
  "mixed-90": {
868
969
  type: "mixed-90",
869
970
  minFrames: 90,
870
971
  recommendedFrames: 90,
871
972
  description: "Mixed 90-frame model",
872
- deprecated: true
973
+ deprecated: true,
974
+ aliases: ["v1"],
975
+ sunsetDate: "2026-09-01",
976
+ replacement: "mixed-90-v2"
873
977
  },
874
978
  "mixed-120": {
875
979
  type: "mixed-120",
876
980
  minFrames: 120,
877
981
  recommendedFrames: 120,
878
982
  description: "Mixed 120-frame model",
879
- deprecated: true
983
+ deprecated: true,
984
+ aliases: ["v1"],
985
+ sunsetDate: "2026-09-01",
986
+ replacement: "mixed-120-v2"
880
987
  },
881
988
  "mixed-150": {
882
989
  type: "mixed-150",
883
990
  minFrames: 150,
884
991
  recommendedFrames: 150,
885
992
  description: "Mixed 150-frame model",
886
- deprecated: true
993
+ deprecated: true,
994
+ aliases: ["v1"],
995
+ sunsetDate: "2026-09-01"
887
996
  },
888
997
  "mixed-250": {
889
998
  type: "mixed-250",
890
999
  minFrames: 250,
891
1000
  recommendedFrames: 250,
892
1001
  description: "Mixed 250-frame model",
893
- deprecated: true
1002
+ deprecated: true,
1003
+ aliases: ["v1"],
1004
+ sunsetDate: "2026-09-01"
894
1005
  },
895
1006
  // Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
896
1007
  "mixed-10-v2": {
@@ -898,35 +1009,40 @@ var MODEL_CONFIGS = {
898
1009
  minFrames: 10,
899
1010
  recommendedFrames: 10,
900
1011
  description: "Mixed V2 10-frame model",
901
- deprecated: false
1012
+ deprecated: false,
1013
+ aliases: ["latest", "v2"]
902
1014
  },
903
1015
  "mixed-30-v2": {
904
1016
  type: "mixed-30-v2",
905
1017
  minFrames: 30,
906
1018
  recommendedFrames: 30,
907
1019
  description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
908
- deprecated: false
1020
+ deprecated: false,
1021
+ aliases: ["latest", "v2"]
909
1022
  },
910
1023
  "mixed-60-v2": {
911
1024
  type: "mixed-60-v2",
912
1025
  minFrames: 60,
913
1026
  recommendedFrames: 60,
914
1027
  description: "Mixed V2 60-frame model",
915
- deprecated: false
1028
+ deprecated: false,
1029
+ aliases: ["latest", "v2"]
916
1030
  },
917
1031
  "mixed-90-v2": {
918
1032
  type: "mixed-90-v2",
919
1033
  minFrames: 90,
920
1034
  recommendedFrames: 90,
921
1035
  description: "Mixed V2 90-frame model",
922
- deprecated: false
1036
+ deprecated: false,
1037
+ aliases: ["latest", "v2"]
923
1038
  },
924
1039
  "mixed-120-v2": {
925
1040
  type: "mixed-120-v2",
926
1041
  minFrames: 120,
927
1042
  recommendedFrames: 120,
928
1043
  description: "Mixed V2 120-frame model",
929
- deprecated: false
1044
+ deprecated: false,
1045
+ aliases: ["latest", "v2"]
930
1046
  }
931
1047
  };
932
1048
  var HYBRID_MODEL_CONFIGS = {
@@ -1226,7 +1342,8 @@ function getCaptureQualityFeedback(state) {
1226
1342
  tooCloseToIdeal,
1227
1343
  cameraAngleLow,
1228
1344
  cameraAngleHigh,
1229
- cameraTilted
1345
+ cameraTilted,
1346
+ faceAreaLow
1230
1347
  } = state;
1231
1348
  if (!hasFace) {
1232
1349
  return FEEDBACK_MESSAGES.no_face;
@@ -1243,7 +1360,7 @@ function getCaptureQualityFeedback(state) {
1243
1360
  if (tooClose) {
1244
1361
  return FEEDBACK_MESSAGES.too_close;
1245
1362
  }
1246
- if (tooFar) {
1363
+ if (tooFar || faceAreaLow) {
1247
1364
  return FEEDBACK_MESSAGES.too_far;
1248
1365
  }
1249
1366
  if (isPartialFace) {
@@ -1284,9 +1401,10 @@ function canCaptureFrame(state) {
1284
1401
  tooCloseToIdeal,
1285
1402
  cameraAngleLow,
1286
1403
  cameraAngleHigh,
1287
- cameraTilted
1404
+ cameraTilted,
1405
+ faceAreaLow
1288
1406
  } = state;
1289
- return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
1407
+ return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted && !faceAreaLow;
1290
1408
  }
1291
1409
 
1292
1410
  // src/utils/validators.ts
@@ -1370,6 +1488,7 @@ var FACE_CENTER_VERTICAL_OFFSET = 0.05;
1370
1488
  var MIN_IDEAL_FACE_RATIO = 0.05;
1371
1489
  var MAX_IDEAL_FACE_RATIO = 0.2;
1372
1490
  var MIN_FACE_RATIO = 0.036;
1491
+ var MIN_FACE_AREA_RATIO = 0.04;
1373
1492
  var MAX_FACE_RATIO = 0.7;
1374
1493
  var FACE_CROP_OUTPUT_SIZE = 224;
1375
1494
  var MAX_FACE_PERCENTAGE_IN_CROP = 0.45;
@@ -1935,6 +2054,7 @@ export {
1935
2054
  MAX_IDEAL_FACE_RATIO,
1936
2055
  MIN_CAPTURE_ALIGNMENT,
1937
2056
  MIN_CROP_MULTIPLIER,
2057
+ MIN_FACE_AREA_RATIO,
1938
2058
  MIN_FACE_BOTTOM_MARGIN,
1939
2059
  MIN_FACE_RATIO,
1940
2060
  MIN_FACE_SIDE_MARGIN,
@@ -1948,6 +2068,7 @@ export {
1948
2068
  OVAL_REGION_MOBILE,
1949
2069
  RETRY_CONFIG,
1950
2070
  TARGET_FACE_PERCENTAGE_IN_CROP,
2071
+ VALID_FRAME_COUNTS,
1951
2072
  analyzeBlur,
1952
2073
  analyzeEyeRegionBrightness,
1953
2074
  analyzeEyeRegionContrast,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moveris/shared",
3
- "version": "2.9.0",
3
+ "version": "3.1.0",
4
4
  "description": "Core business logic for Moveris Live SDK",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",