@moveris/shared 2.9.0 → 3.0.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.js CHANGED
@@ -85,6 +85,7 @@ __export(index_exports, {
85
85
  OVAL_REGION_MOBILE: () => OVAL_REGION_MOBILE,
86
86
  RETRY_CONFIG: () => RETRY_CONFIG,
87
87
  TARGET_FACE_PERCENTAGE_IN_CROP: () => TARGET_FACE_PERCENTAGE_IN_CROP,
88
+ VALID_FRAME_COUNTS: () => VALID_FRAME_COUNTS,
88
89
  analyzeBlur: () => analyzeBlur,
89
90
  analyzeEyeRegionBrightness: () => analyzeEyeRegionBrightness,
90
91
  analyzeEyeRegionContrast: () => analyzeEyeRegionContrast,
@@ -171,7 +172,8 @@ var FRAME_BUFFER_CONFIG = {
171
172
  var AUTH_CONFIG = {
172
173
  timeout: 3e4,
173
174
  // 30 seconds for API requests
174
- apiKeyHeader: "X-API-Key"
175
+ apiKeyHeader: "X-API-Key",
176
+ modelVersionHeader: "X-Model-Version"
175
177
  };
176
178
  var FRAME_CONFIG = {
177
179
  targetFPS: 30,
@@ -300,14 +302,22 @@ var LivenessClient = class _LivenessClient {
300
302
  constructor(config) {
301
303
  this.baseUrl = (config.baseUrl ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
302
304
  this.apiKey = config.apiKey;
305
+ this.modelVersion = config.modelVersion;
303
306
  this.timeout = config.timeout ?? AUTH_CONFIG.timeout;
304
307
  this.enableRetry = config.enableRetry ?? true;
305
308
  this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
306
309
  }
307
310
  /**
308
- * Make an authenticated API request
311
+ * Make an authenticated API request, returning the parsed body.
309
312
  */
310
313
  async request(path, options = {}) {
314
+ const { data } = await this.requestRaw(path, options);
315
+ return data;
316
+ }
317
+ /**
318
+ * Make an authenticated API request, returning both parsed body and headers.
319
+ */
320
+ async requestRaw(path, options = {}) {
311
321
  const url = `${this.baseUrl}${path}`;
312
322
  const controller = new AbortController();
313
323
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@@ -325,7 +335,8 @@ var LivenessClient = class _LivenessClient {
325
335
  if (!response.ok) {
326
336
  throw await this.parseErrorResponse(response);
327
337
  }
328
- return await response.json();
338
+ const data = await response.json();
339
+ return { data, headers: response.headers };
329
340
  } catch (error) {
330
341
  clearTimeout(timeoutId);
331
342
  if (error instanceof LivenessApiError) {
@@ -410,6 +421,30 @@ var LivenessClient = class _LivenessClient {
410
421
  const match = /'error'\s*:\s*'([^']+)'/.exec(text) ?? /"error"\s*:\s*"([^"]+)"/.exec(text);
411
422
  return match?.[1] ?? null;
412
423
  }
424
+ /**
425
+ * Parse deprecation-related headers from an API response.
426
+ * Returns undefined when no deprecation info is present.
427
+ */
428
+ static parseDeprecationHeaders(headers) {
429
+ const resolved = headers.get("x-moveris-model-resolved");
430
+ if (!resolved) return void 0;
431
+ const deprecated = headers.get("deprecation") === "true";
432
+ return {
433
+ deprecated,
434
+ resolvedModel: resolved,
435
+ deprecatedModel: headers.get("x-moveris-deprecated-model") ?? void 0,
436
+ sunsetDate: headers.get("sunset") ?? void 0,
437
+ suggestedModel: headers.get("x-moveris-suggested-model") ?? void 0
438
+ };
439
+ }
440
+ /**
441
+ * Build extra request headers for model versioning.
442
+ */
443
+ buildModelVersionHeaders(modelVersion) {
444
+ const version = modelVersion ?? this.modelVersion;
445
+ if (!version) return {};
446
+ return { [AUTH_CONFIG.modelVersionHeader]: version };
447
+ }
413
448
  /**
414
449
  * Make a request with optional retry
415
450
  */
@@ -424,6 +459,20 @@ var LivenessClient = class _LivenessClient {
424
459
  }
425
460
  return this.request(path, options);
426
461
  }
462
+ /**
463
+ * Make a request with optional retry, returning both data and headers.
464
+ */
465
+ async requestWithRetryRaw(path, options = {}) {
466
+ if (this.enableRetry) {
467
+ return retryWithBackoff(() => this.requestRaw(path, options), {
468
+ maxAttempts: RETRY_CONFIG.maxAttempts,
469
+ initialDelay: RETRY_CONFIG.initialDelay,
470
+ maxDelay: RETRY_CONFIG.maxDelay,
471
+ backoffMultiplier: RETRY_CONFIG.backoffMultiplier
472
+ });
473
+ }
474
+ return this.requestRaw(path, options);
475
+ }
427
476
  // ===========================================================================
428
477
  // Health & Status
429
478
  // ===========================================================================
@@ -458,18 +507,31 @@ var LivenessClient = class _LivenessClient {
458
507
  * @returns Liveness result
459
508
  */
460
509
  async fastCheck(frames, options = {}) {
510
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
461
511
  const request = {
462
512
  session_id: options.sessionId ?? generateSessionId(),
463
513
  model: options.model ?? "10",
464
514
  source: options.source ?? "live",
465
515
  frames: toFrameData(frames),
516
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
466
517
  ...options.warnings?.length ? { warnings: options.warnings } : {}
467
518
  };
468
- const response = await this.requestWithRetry(API_PATHS.fastCheck, {
469
- method: "POST",
470
- body: JSON.stringify(request)
471
- });
472
- return toLivenessResult(response);
519
+ const { data: response, headers } = await this.requestWithRetryRaw(
520
+ API_PATHS.fastCheck,
521
+ {
522
+ method: "POST",
523
+ body: JSON.stringify(request),
524
+ headers: this.buildModelVersionHeaders(effectiveVersion)
525
+ }
526
+ );
527
+ const result = toLivenessResult(response);
528
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
529
+ if (result.deprecation?.deprecated) {
530
+ console.warn(
531
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
532
+ );
533
+ }
534
+ return result;
473
535
  }
474
536
  /**
475
537
  * Perform fast liveness check with pre-cropped face images
@@ -479,19 +541,32 @@ var LivenessClient = class _LivenessClient {
479
541
  * @returns Liveness result
480
542
  */
481
543
  async fastCheckCrops(crops, options = {}) {
544
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
482
545
  const request = {
483
546
  session_id: options.sessionId ?? generateSessionId(),
484
547
  model: options.model ?? "10",
485
548
  source: options.source ?? "live",
486
549
  crops,
550
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
487
551
  ...options.warnings?.length ? { warnings: options.warnings } : {},
488
552
  ...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
489
553
  };
490
- const response = await this.requestWithRetry(API_PATHS.fastCheckCrops, {
491
- method: "POST",
492
- body: JSON.stringify(request)
493
- });
494
- return toLivenessResult(response);
554
+ const { data: response, headers } = await this.requestWithRetryRaw(
555
+ API_PATHS.fastCheckCrops,
556
+ {
557
+ method: "POST",
558
+ body: JSON.stringify(request),
559
+ headers: this.buildModelVersionHeaders(effectiveVersion)
560
+ }
561
+ );
562
+ const result = toLivenessResult(response);
563
+ result.deprecation = _LivenessClient.parseDeprecationHeaders(headers);
564
+ if (result.deprecation?.deprecated) {
565
+ console.warn(
566
+ `[Moveris] Model "${result.deprecation.resolvedModel}" is deprecated.` + (result.deprecation.suggestedModel ? ` Migrate to "${result.deprecation.suggestedModel}".` : "") + (result.deprecation.sunsetDate ? ` Sunset date: ${result.deprecation.sunsetDate}.` : "")
567
+ );
568
+ }
569
+ return result;
495
570
  }
496
571
  /**
497
572
  * Send a single captured frame to the streaming endpoint.
@@ -519,28 +594,29 @@ var LivenessClient = class _LivenessClient {
519
594
  return this.sendStreamFrameInternal(frameData, {
520
595
  sessionId: options.sessionId,
521
596
  model: options.model ?? "10",
597
+ modelVersion: options.modelVersion,
598
+ frameCount: options.frameCount,
522
599
  source: options.source ?? "live",
523
600
  warnings: options.warnings
524
601
  });
525
602
  }
526
603
  /**
527
604
  * Send a single FrameData to the streaming endpoint with retry (internal)
528
- *
529
- * @param frame - Single frame to send
530
- * @param options - Session and model options
531
- * @returns Stream response with status
532
605
  */
533
606
  async sendStreamFrameInternal(frameData, options) {
607
+ const effectiveVersion = options.modelVersion ?? this.modelVersion;
534
608
  const request = {
535
609
  session_id: options.sessionId,
536
610
  model: options.model,
537
611
  source: options.source,
538
612
  frame: frameData,
613
+ ...options.frameCount != null ? { frame_count: options.frameCount } : {},
539
614
  ...options.warnings?.length ? { warnings: options.warnings } : {}
540
615
  };
541
616
  return this.requestWithRetry(API_PATHS.fastCheckStream, {
542
617
  method: "POST",
543
- body: JSON.stringify(request)
618
+ body: JSON.stringify(request),
619
+ headers: this.buildModelVersionHeaders(effectiveVersion)
544
620
  });
545
621
  }
546
622
  /**
@@ -565,6 +641,8 @@ var LivenessClient = class _LivenessClient {
565
641
  const response = await this.sendStreamFrameInternal(frameData, {
566
642
  sessionId,
567
643
  model,
644
+ modelVersion: options.modelVersion,
645
+ frameCount: options.frameCount,
568
646
  source,
569
647
  warnings: options.warnings
570
648
  });
@@ -615,6 +693,8 @@ var LivenessClient = class _LivenessClient {
615
693
  const response = await this.sendStreamFrameInternal(frameData, {
616
694
  sessionId,
617
695
  model,
696
+ modelVersion: options.modelVersion,
697
+ frameCount: options.frameCount,
618
698
  source,
619
699
  warnings: options.warnings
620
700
  });
@@ -891,6 +971,7 @@ var FrameQueue = class {
891
971
  };
892
972
 
893
973
  // src/types/models.ts
974
+ var VALID_FRAME_COUNTS = [10, 30, 60, 90, 120];
894
975
  var MODEL_CONFIGS = {
895
976
  // Standard fast-check models
896
977
  "10": {
@@ -898,21 +979,24 @@ var MODEL_CONFIGS = {
898
979
  minFrames: 10,
899
980
  recommendedFrames: 10,
900
981
  description: "Fast model - 10 frames, quick verification",
901
- deprecated: false
982
+ deprecated: false,
983
+ aliases: ["fast"]
902
984
  },
903
985
  "50": {
904
986
  type: "50",
905
987
  minFrames: 50,
906
988
  recommendedFrames: 50,
907
989
  description: "Balanced model - 50 frames, good accuracy",
908
- deprecated: false
990
+ deprecated: false,
991
+ aliases: ["spatial"]
909
992
  },
910
993
  "250": {
911
994
  type: "250",
912
995
  minFrames: 250,
913
996
  recommendedFrames: 250,
914
997
  description: "High-accuracy model - 250 frames, best accuracy",
915
- deprecated: false
998
+ deprecated: false,
999
+ aliases: ["spatial"]
916
1000
  },
917
1001
  // Hybrid V2 models with physiological features
918
1002
  "hybrid-v2-10": {
@@ -920,63 +1004,72 @@ var MODEL_CONFIGS = {
920
1004
  minFrames: 10,
921
1005
  recommendedFrames: 10,
922
1006
  description: "Hybrid V2 10-frame model with physio features",
923
- deprecated: false
1007
+ deprecated: false,
1008
+ aliases: ["hybrid"]
924
1009
  },
925
1010
  "hybrid-v2-30": {
926
1011
  type: "hybrid-v2-30",
927
1012
  minFrames: 30,
928
1013
  recommendedFrames: 30,
929
1014
  description: "Hybrid V2 30-frame model with physio features",
930
- deprecated: false
1015
+ deprecated: false,
1016
+ aliases: ["hybrid"]
931
1017
  },
932
1018
  "hybrid-v2-50": {
933
1019
  type: "hybrid-v2-50",
934
1020
  minFrames: 50,
935
1021
  recommendedFrames: 50,
936
1022
  description: "Hybrid V2 50-frame model with physio features",
937
- deprecated: false
1023
+ deprecated: false,
1024
+ aliases: ["hybrid"]
938
1025
  },
939
1026
  "hybrid-v2-60": {
940
1027
  type: "hybrid-v2-60",
941
1028
  minFrames: 60,
942
1029
  recommendedFrames: 60,
943
1030
  description: "Hybrid V2 60-frame model with physio features",
944
- deprecated: false
1031
+ deprecated: false,
1032
+ aliases: ["hybrid"]
945
1033
  },
946
1034
  "hybrid-v2-90": {
947
1035
  type: "hybrid-v2-90",
948
1036
  minFrames: 90,
949
1037
  recommendedFrames: 90,
950
1038
  description: "Hybrid V2 90-frame model with physio features",
951
- deprecated: false
1039
+ deprecated: false,
1040
+ aliases: ["hybrid"]
952
1041
  },
953
1042
  "hybrid-v2-100": {
954
1043
  type: "hybrid-v2-100",
955
1044
  minFrames: 100,
956
1045
  recommendedFrames: 100,
957
1046
  description: "Hybrid V2 100-frame model with physio features",
958
- deprecated: false
1047
+ deprecated: false,
1048
+ aliases: ["hybrid"]
959
1049
  },
960
1050
  "hybrid-v2-125": {
961
1051
  type: "hybrid-v2-125",
962
1052
  minFrames: 125,
963
1053
  recommendedFrames: 125,
964
1054
  description: "Hybrid V2 125-frame model with physio features",
965
- deprecated: false
1055
+ deprecated: false,
1056
+ aliases: ["hybrid"]
966
1057
  },
967
1058
  "hybrid-v2-150": {
968
1059
  type: "hybrid-v2-150",
969
1060
  minFrames: 150,
970
1061
  recommendedFrames: 150,
971
1062
  description: "Hybrid V2 150-frame model with physio features",
972
- deprecated: false
1063
+ deprecated: false,
1064
+ aliases: ["hybrid"]
973
1065
  },
974
1066
  "hybrid-v2-250": {
975
1067
  type: "hybrid-v2-250",
976
1068
  minFrames: 250,
977
1069
  recommendedFrames: 250,
978
1070
  description: "Hybrid V2 250-frame model with physio features",
979
- deprecated: false
1071
+ deprecated: false,
1072
+ aliases: ["hybrid"]
980
1073
  },
981
1074
  // Mixed V1 models — deprecated, use mixed-*-v2 equivalents instead
982
1075
  "mixed-10": {
@@ -984,49 +1077,68 @@ var MODEL_CONFIGS = {
984
1077
  minFrames: 10,
985
1078
  recommendedFrames: 10,
986
1079
  description: "Mixed 10-frame model",
987
- deprecated: true
1080
+ deprecated: true,
1081
+ aliases: ["v1"],
1082
+ sunsetDate: "2026-09-01",
1083
+ replacement: "mixed-10-v2"
988
1084
  },
989
1085
  "mixed-30": {
990
1086
  type: "mixed-30",
991
1087
  minFrames: 30,
992
1088
  recommendedFrames: 30,
993
1089
  description: "Mixed 30-frame model",
994
- deprecated: true
1090
+ deprecated: true,
1091
+ aliases: ["v1"],
1092
+ sunsetDate: "2026-09-01",
1093
+ replacement: "mixed-30-v2"
995
1094
  },
996
1095
  "mixed-60": {
997
1096
  type: "mixed-60",
998
1097
  minFrames: 60,
999
1098
  recommendedFrames: 60,
1000
1099
  description: "Mixed 60-frame model",
1001
- deprecated: true
1100
+ deprecated: true,
1101
+ aliases: ["v1"],
1102
+ sunsetDate: "2026-09-01",
1103
+ replacement: "mixed-60-v2"
1002
1104
  },
1003
1105
  "mixed-90": {
1004
1106
  type: "mixed-90",
1005
1107
  minFrames: 90,
1006
1108
  recommendedFrames: 90,
1007
1109
  description: "Mixed 90-frame model",
1008
- deprecated: true
1110
+ deprecated: true,
1111
+ aliases: ["v1"],
1112
+ sunsetDate: "2026-09-01",
1113
+ replacement: "mixed-90-v2"
1009
1114
  },
1010
1115
  "mixed-120": {
1011
1116
  type: "mixed-120",
1012
1117
  minFrames: 120,
1013
1118
  recommendedFrames: 120,
1014
1119
  description: "Mixed 120-frame model",
1015
- deprecated: true
1120
+ deprecated: true,
1121
+ aliases: ["v1"],
1122
+ sunsetDate: "2026-09-01",
1123
+ replacement: "mixed-120-v2"
1016
1124
  },
1017
1125
  "mixed-150": {
1018
1126
  type: "mixed-150",
1019
1127
  minFrames: 150,
1020
1128
  recommendedFrames: 150,
1021
1129
  description: "Mixed 150-frame model",
1022
- deprecated: true
1130
+ deprecated: true,
1131
+ aliases: ["v1"],
1132
+ sunsetDate: "2026-09-01"
1023
1133
  },
1024
1134
  "mixed-250": {
1025
1135
  type: "mixed-250",
1026
1136
  minFrames: 250,
1027
1137
  recommendedFrames: 250,
1028
1138
  description: "Mixed 250-frame model",
1029
- deprecated: true
1139
+ deprecated: true,
1140
+ aliases: ["v1"],
1141
+ sunsetDate: "2026-09-01"
1030
1142
  },
1031
1143
  // Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
1032
1144
  "mixed-10-v2": {
@@ -1034,35 +1146,40 @@ var MODEL_CONFIGS = {
1034
1146
  minFrames: 10,
1035
1147
  recommendedFrames: 10,
1036
1148
  description: "Mixed V2 10-frame model",
1037
- deprecated: false
1149
+ deprecated: false,
1150
+ aliases: ["latest", "v2"]
1038
1151
  },
1039
1152
  "mixed-30-v2": {
1040
1153
  type: "mixed-30-v2",
1041
1154
  minFrames: 30,
1042
1155
  recommendedFrames: 30,
1043
1156
  description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
1044
- deprecated: false
1157
+ deprecated: false,
1158
+ aliases: ["latest", "v2"]
1045
1159
  },
1046
1160
  "mixed-60-v2": {
1047
1161
  type: "mixed-60-v2",
1048
1162
  minFrames: 60,
1049
1163
  recommendedFrames: 60,
1050
1164
  description: "Mixed V2 60-frame model",
1051
- deprecated: false
1165
+ deprecated: false,
1166
+ aliases: ["latest", "v2"]
1052
1167
  },
1053
1168
  "mixed-90-v2": {
1054
1169
  type: "mixed-90-v2",
1055
1170
  minFrames: 90,
1056
1171
  recommendedFrames: 90,
1057
1172
  description: "Mixed V2 90-frame model",
1058
- deprecated: false
1173
+ deprecated: false,
1174
+ aliases: ["latest", "v2"]
1059
1175
  },
1060
1176
  "mixed-120-v2": {
1061
1177
  type: "mixed-120-v2",
1062
1178
  minFrames: 120,
1063
1179
  recommendedFrames: 120,
1064
1180
  description: "Mixed V2 120-frame model",
1065
- deprecated: false
1181
+ deprecated: false,
1182
+ aliases: ["latest", "v2"]
1066
1183
  }
1067
1184
  };
1068
1185
  var HYBRID_MODEL_CONFIGS = {
@@ -2085,6 +2202,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
2085
2202
  OVAL_REGION_MOBILE,
2086
2203
  RETRY_CONFIG,
2087
2204
  TARGET_FACE_PERCENTAGE_IN_CROP,
2205
+ VALID_FRAME_COUNTS,
2088
2206
  analyzeBlur,
2089
2207
  analyzeEyeRegionBrightness,
2090
2208
  analyzeEyeRegionContrast,