@moveris/shared 2.7.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/README.md +169 -20
- package/dist/index.d.mts +52 -6
- package/dist/index.d.ts +52 -6
- package/dist/index.js +215 -59
- package/dist/index.mjs +211 -59
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,6 +31,8 @@ __export(index_exports, {
|
|
|
31
31
|
BACKLIT_RATIO_THRESHOLD: () => BACKLIT_RATIO_THRESHOLD,
|
|
32
32
|
BLUR_THRESHOLD_MOBILE: () => BLUR_THRESHOLD_MOBILE,
|
|
33
33
|
BaseFrameCollector: () => BaseFrameCollector,
|
|
34
|
+
CAMERA_ANGLE_HIGH_RATIO: () => CAMERA_ANGLE_HIGH_RATIO,
|
|
35
|
+
CAMERA_ANGLE_LOW_RATIO: () => CAMERA_ANGLE_LOW_RATIO,
|
|
34
36
|
DEFAULT_BLUR_THRESHOLD: () => DEFAULT_BLUR_THRESHOLD,
|
|
35
37
|
DEFAULT_ENDPOINT: () => DEFAULT_ENDPOINT,
|
|
36
38
|
DEFAULT_FACE_DETECTION_TIERS: () => DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -83,6 +85,7 @@ __export(index_exports, {
|
|
|
83
85
|
OVAL_REGION_MOBILE: () => OVAL_REGION_MOBILE,
|
|
84
86
|
RETRY_CONFIG: () => RETRY_CONFIG,
|
|
85
87
|
TARGET_FACE_PERCENTAGE_IN_CROP: () => TARGET_FACE_PERCENTAGE_IN_CROP,
|
|
88
|
+
VALID_FRAME_COUNTS: () => VALID_FRAME_COUNTS,
|
|
86
89
|
analyzeBlur: () => analyzeBlur,
|
|
87
90
|
analyzeEyeRegionBrightness: () => analyzeEyeRegionBrightness,
|
|
88
91
|
analyzeEyeRegionContrast: () => analyzeEyeRegionContrast,
|
|
@@ -95,6 +98,7 @@ __export(index_exports, {
|
|
|
95
98
|
checkEyeRegionQuality: () => checkEyeRegionQuality,
|
|
96
99
|
checkFrameQuality: () => checkFrameQuality,
|
|
97
100
|
decodeBase64: () => decodeBase64,
|
|
101
|
+
detectCameraAngle: () => detectCameraAngle,
|
|
98
102
|
detectFaceRoll: () => detectFaceRoll,
|
|
99
103
|
detectSpecularHighlights: () => detectSpecularHighlights,
|
|
100
104
|
encodeBase64: () => encodeBase64,
|
|
@@ -168,7 +172,8 @@ var FRAME_BUFFER_CONFIG = {
|
|
|
168
172
|
var AUTH_CONFIG = {
|
|
169
173
|
timeout: 3e4,
|
|
170
174
|
// 30 seconds for API requests
|
|
171
|
-
apiKeyHeader: "X-API-Key"
|
|
175
|
+
apiKeyHeader: "X-API-Key",
|
|
176
|
+
modelVersionHeader: "X-Model-Version"
|
|
172
177
|
};
|
|
173
178
|
var FRAME_CONFIG = {
|
|
174
179
|
targetFPS: 30,
|
|
@@ -297,14 +302,22 @@ var LivenessClient = class _LivenessClient {
|
|
|
297
302
|
constructor(config) {
|
|
298
303
|
this.baseUrl = (config.baseUrl ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
|
|
299
304
|
this.apiKey = config.apiKey;
|
|
305
|
+
this.modelVersion = config.modelVersion;
|
|
300
306
|
this.timeout = config.timeout ?? AUTH_CONFIG.timeout;
|
|
301
307
|
this.enableRetry = config.enableRetry ?? true;
|
|
302
308
|
this.fetchFn = config.customFetch ?? (typeof window !== "undefined" ? fetch.bind(window) : fetch);
|
|
303
309
|
}
|
|
304
310
|
/**
|
|
305
|
-
* Make an authenticated API request
|
|
311
|
+
* Make an authenticated API request, returning the parsed body.
|
|
306
312
|
*/
|
|
307
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 = {}) {
|
|
308
321
|
const url = `${this.baseUrl}${path}`;
|
|
309
322
|
const controller = new AbortController();
|
|
310
323
|
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
@@ -322,7 +335,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
322
335
|
if (!response.ok) {
|
|
323
336
|
throw await this.parseErrorResponse(response);
|
|
324
337
|
}
|
|
325
|
-
|
|
338
|
+
const data = await response.json();
|
|
339
|
+
return { data, headers: response.headers };
|
|
326
340
|
} catch (error) {
|
|
327
341
|
clearTimeout(timeoutId);
|
|
328
342
|
if (error instanceof LivenessApiError) {
|
|
@@ -407,6 +421,30 @@ var LivenessClient = class _LivenessClient {
|
|
|
407
421
|
const match = /'error'\s*:\s*'([^']+)'/.exec(text) ?? /"error"\s*:\s*"([^"]+)"/.exec(text);
|
|
408
422
|
return match?.[1] ?? null;
|
|
409
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
|
+
}
|
|
410
448
|
/**
|
|
411
449
|
* Make a request with optional retry
|
|
412
450
|
*/
|
|
@@ -421,6 +459,20 @@ var LivenessClient = class _LivenessClient {
|
|
|
421
459
|
}
|
|
422
460
|
return this.request(path, options);
|
|
423
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
|
+
}
|
|
424
476
|
// ===========================================================================
|
|
425
477
|
// Health & Status
|
|
426
478
|
// ===========================================================================
|
|
@@ -455,18 +507,31 @@ var LivenessClient = class _LivenessClient {
|
|
|
455
507
|
* @returns Liveness result
|
|
456
508
|
*/
|
|
457
509
|
async fastCheck(frames, options = {}) {
|
|
510
|
+
const effectiveVersion = options.modelVersion ?? this.modelVersion;
|
|
458
511
|
const request = {
|
|
459
512
|
session_id: options.sessionId ?? generateSessionId(),
|
|
460
513
|
model: options.model ?? "10",
|
|
461
514
|
source: options.source ?? "live",
|
|
462
515
|
frames: toFrameData(frames),
|
|
516
|
+
...options.frameCount != null ? { frame_count: options.frameCount } : {},
|
|
463
517
|
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
464
518
|
};
|
|
465
|
-
const response = await this.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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;
|
|
470
535
|
}
|
|
471
536
|
/**
|
|
472
537
|
* Perform fast liveness check with pre-cropped face images
|
|
@@ -476,18 +541,32 @@ var LivenessClient = class _LivenessClient {
|
|
|
476
541
|
* @returns Liveness result
|
|
477
542
|
*/
|
|
478
543
|
async fastCheckCrops(crops, options = {}) {
|
|
544
|
+
const effectiveVersion = options.modelVersion ?? this.modelVersion;
|
|
479
545
|
const request = {
|
|
480
546
|
session_id: options.sessionId ?? generateSessionId(),
|
|
481
547
|
model: options.model ?? "10",
|
|
482
548
|
source: options.source ?? "live",
|
|
483
549
|
crops,
|
|
484
|
-
...options.
|
|
550
|
+
...options.frameCount != null ? { frame_count: options.frameCount } : {},
|
|
551
|
+
...options.warnings?.length ? { warnings: options.warnings } : {},
|
|
552
|
+
...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
|
|
485
553
|
};
|
|
486
|
-
const response = await this.
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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;
|
|
491
570
|
}
|
|
492
571
|
/**
|
|
493
572
|
* Send a single captured frame to the streaming endpoint.
|
|
@@ -515,28 +594,29 @@ var LivenessClient = class _LivenessClient {
|
|
|
515
594
|
return this.sendStreamFrameInternal(frameData, {
|
|
516
595
|
sessionId: options.sessionId,
|
|
517
596
|
model: options.model ?? "10",
|
|
597
|
+
modelVersion: options.modelVersion,
|
|
598
|
+
frameCount: options.frameCount,
|
|
518
599
|
source: options.source ?? "live",
|
|
519
600
|
warnings: options.warnings
|
|
520
601
|
});
|
|
521
602
|
}
|
|
522
603
|
/**
|
|
523
604
|
* Send a single FrameData to the streaming endpoint with retry (internal)
|
|
524
|
-
*
|
|
525
|
-
* @param frame - Single frame to send
|
|
526
|
-
* @param options - Session and model options
|
|
527
|
-
* @returns Stream response with status
|
|
528
605
|
*/
|
|
529
606
|
async sendStreamFrameInternal(frameData, options) {
|
|
607
|
+
const effectiveVersion = options.modelVersion ?? this.modelVersion;
|
|
530
608
|
const request = {
|
|
531
609
|
session_id: options.sessionId,
|
|
532
610
|
model: options.model,
|
|
533
611
|
source: options.source,
|
|
534
612
|
frame: frameData,
|
|
613
|
+
...options.frameCount != null ? { frame_count: options.frameCount } : {},
|
|
535
614
|
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
536
615
|
};
|
|
537
616
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
538
617
|
method: "POST",
|
|
539
|
-
body: JSON.stringify(request)
|
|
618
|
+
body: JSON.stringify(request),
|
|
619
|
+
headers: this.buildModelVersionHeaders(effectiveVersion)
|
|
540
620
|
});
|
|
541
621
|
}
|
|
542
622
|
/**
|
|
@@ -561,6 +641,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
561
641
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
562
642
|
sessionId,
|
|
563
643
|
model,
|
|
644
|
+
modelVersion: options.modelVersion,
|
|
645
|
+
frameCount: options.frameCount,
|
|
564
646
|
source,
|
|
565
647
|
warnings: options.warnings
|
|
566
648
|
});
|
|
@@ -611,6 +693,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
611
693
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
612
694
|
sessionId,
|
|
613
695
|
model,
|
|
696
|
+
modelVersion: options.modelVersion,
|
|
697
|
+
frameCount: options.frameCount,
|
|
614
698
|
source,
|
|
615
699
|
warnings: options.warnings
|
|
616
700
|
});
|
|
@@ -887,6 +971,7 @@ var FrameQueue = class {
|
|
|
887
971
|
};
|
|
888
972
|
|
|
889
973
|
// src/types/models.ts
|
|
974
|
+
var VALID_FRAME_COUNTS = [10, 30, 60, 90, 120];
|
|
890
975
|
var MODEL_CONFIGS = {
|
|
891
976
|
// Standard fast-check models
|
|
892
977
|
"10": {
|
|
@@ -894,21 +979,24 @@ var MODEL_CONFIGS = {
|
|
|
894
979
|
minFrames: 10,
|
|
895
980
|
recommendedFrames: 10,
|
|
896
981
|
description: "Fast model - 10 frames, quick verification",
|
|
897
|
-
deprecated: false
|
|
982
|
+
deprecated: false,
|
|
983
|
+
aliases: ["fast"]
|
|
898
984
|
},
|
|
899
985
|
"50": {
|
|
900
986
|
type: "50",
|
|
901
987
|
minFrames: 50,
|
|
902
988
|
recommendedFrames: 50,
|
|
903
989
|
description: "Balanced model - 50 frames, good accuracy",
|
|
904
|
-
deprecated: false
|
|
990
|
+
deprecated: false,
|
|
991
|
+
aliases: ["spatial"]
|
|
905
992
|
},
|
|
906
993
|
"250": {
|
|
907
994
|
type: "250",
|
|
908
995
|
minFrames: 250,
|
|
909
996
|
recommendedFrames: 250,
|
|
910
997
|
description: "High-accuracy model - 250 frames, best accuracy",
|
|
911
|
-
deprecated: false
|
|
998
|
+
deprecated: false,
|
|
999
|
+
aliases: ["spatial"]
|
|
912
1000
|
},
|
|
913
1001
|
// Hybrid V2 models with physiological features
|
|
914
1002
|
"hybrid-v2-10": {
|
|
@@ -916,63 +1004,72 @@ var MODEL_CONFIGS = {
|
|
|
916
1004
|
minFrames: 10,
|
|
917
1005
|
recommendedFrames: 10,
|
|
918
1006
|
description: "Hybrid V2 10-frame model with physio features",
|
|
919
|
-
deprecated: false
|
|
1007
|
+
deprecated: false,
|
|
1008
|
+
aliases: ["hybrid"]
|
|
920
1009
|
},
|
|
921
1010
|
"hybrid-v2-30": {
|
|
922
1011
|
type: "hybrid-v2-30",
|
|
923
1012
|
minFrames: 30,
|
|
924
1013
|
recommendedFrames: 30,
|
|
925
1014
|
description: "Hybrid V2 30-frame model with physio features",
|
|
926
|
-
deprecated: false
|
|
1015
|
+
deprecated: false,
|
|
1016
|
+
aliases: ["hybrid"]
|
|
927
1017
|
},
|
|
928
1018
|
"hybrid-v2-50": {
|
|
929
1019
|
type: "hybrid-v2-50",
|
|
930
1020
|
minFrames: 50,
|
|
931
1021
|
recommendedFrames: 50,
|
|
932
1022
|
description: "Hybrid V2 50-frame model with physio features",
|
|
933
|
-
deprecated: false
|
|
1023
|
+
deprecated: false,
|
|
1024
|
+
aliases: ["hybrid"]
|
|
934
1025
|
},
|
|
935
1026
|
"hybrid-v2-60": {
|
|
936
1027
|
type: "hybrid-v2-60",
|
|
937
1028
|
minFrames: 60,
|
|
938
1029
|
recommendedFrames: 60,
|
|
939
1030
|
description: "Hybrid V2 60-frame model with physio features",
|
|
940
|
-
deprecated: false
|
|
1031
|
+
deprecated: false,
|
|
1032
|
+
aliases: ["hybrid"]
|
|
941
1033
|
},
|
|
942
1034
|
"hybrid-v2-90": {
|
|
943
1035
|
type: "hybrid-v2-90",
|
|
944
1036
|
minFrames: 90,
|
|
945
1037
|
recommendedFrames: 90,
|
|
946
1038
|
description: "Hybrid V2 90-frame model with physio features",
|
|
947
|
-
deprecated: false
|
|
1039
|
+
deprecated: false,
|
|
1040
|
+
aliases: ["hybrid"]
|
|
948
1041
|
},
|
|
949
1042
|
"hybrid-v2-100": {
|
|
950
1043
|
type: "hybrid-v2-100",
|
|
951
1044
|
minFrames: 100,
|
|
952
1045
|
recommendedFrames: 100,
|
|
953
1046
|
description: "Hybrid V2 100-frame model with physio features",
|
|
954
|
-
deprecated: false
|
|
1047
|
+
deprecated: false,
|
|
1048
|
+
aliases: ["hybrid"]
|
|
955
1049
|
},
|
|
956
1050
|
"hybrid-v2-125": {
|
|
957
1051
|
type: "hybrid-v2-125",
|
|
958
1052
|
minFrames: 125,
|
|
959
1053
|
recommendedFrames: 125,
|
|
960
1054
|
description: "Hybrid V2 125-frame model with physio features",
|
|
961
|
-
deprecated: false
|
|
1055
|
+
deprecated: false,
|
|
1056
|
+
aliases: ["hybrid"]
|
|
962
1057
|
},
|
|
963
1058
|
"hybrid-v2-150": {
|
|
964
1059
|
type: "hybrid-v2-150",
|
|
965
1060
|
minFrames: 150,
|
|
966
1061
|
recommendedFrames: 150,
|
|
967
1062
|
description: "Hybrid V2 150-frame model with physio features",
|
|
968
|
-
deprecated: false
|
|
1063
|
+
deprecated: false,
|
|
1064
|
+
aliases: ["hybrid"]
|
|
969
1065
|
},
|
|
970
1066
|
"hybrid-v2-250": {
|
|
971
1067
|
type: "hybrid-v2-250",
|
|
972
1068
|
minFrames: 250,
|
|
973
1069
|
recommendedFrames: 250,
|
|
974
1070
|
description: "Hybrid V2 250-frame model with physio features",
|
|
975
|
-
deprecated: false
|
|
1071
|
+
deprecated: false,
|
|
1072
|
+
aliases: ["hybrid"]
|
|
976
1073
|
},
|
|
977
1074
|
// Mixed V1 models — deprecated, use mixed-*-v2 equivalents instead
|
|
978
1075
|
"mixed-10": {
|
|
@@ -980,49 +1077,68 @@ var MODEL_CONFIGS = {
|
|
|
980
1077
|
minFrames: 10,
|
|
981
1078
|
recommendedFrames: 10,
|
|
982
1079
|
description: "Mixed 10-frame model",
|
|
983
|
-
deprecated: true
|
|
1080
|
+
deprecated: true,
|
|
1081
|
+
aliases: ["v1"],
|
|
1082
|
+
sunsetDate: "2026-09-01",
|
|
1083
|
+
replacement: "mixed-10-v2"
|
|
984
1084
|
},
|
|
985
1085
|
"mixed-30": {
|
|
986
1086
|
type: "mixed-30",
|
|
987
1087
|
minFrames: 30,
|
|
988
1088
|
recommendedFrames: 30,
|
|
989
1089
|
description: "Mixed 30-frame model",
|
|
990
|
-
deprecated: true
|
|
1090
|
+
deprecated: true,
|
|
1091
|
+
aliases: ["v1"],
|
|
1092
|
+
sunsetDate: "2026-09-01",
|
|
1093
|
+
replacement: "mixed-30-v2"
|
|
991
1094
|
},
|
|
992
1095
|
"mixed-60": {
|
|
993
1096
|
type: "mixed-60",
|
|
994
1097
|
minFrames: 60,
|
|
995
1098
|
recommendedFrames: 60,
|
|
996
1099
|
description: "Mixed 60-frame model",
|
|
997
|
-
deprecated: true
|
|
1100
|
+
deprecated: true,
|
|
1101
|
+
aliases: ["v1"],
|
|
1102
|
+
sunsetDate: "2026-09-01",
|
|
1103
|
+
replacement: "mixed-60-v2"
|
|
998
1104
|
},
|
|
999
1105
|
"mixed-90": {
|
|
1000
1106
|
type: "mixed-90",
|
|
1001
1107
|
minFrames: 90,
|
|
1002
1108
|
recommendedFrames: 90,
|
|
1003
1109
|
description: "Mixed 90-frame model",
|
|
1004
|
-
deprecated: true
|
|
1110
|
+
deprecated: true,
|
|
1111
|
+
aliases: ["v1"],
|
|
1112
|
+
sunsetDate: "2026-09-01",
|
|
1113
|
+
replacement: "mixed-90-v2"
|
|
1005
1114
|
},
|
|
1006
1115
|
"mixed-120": {
|
|
1007
1116
|
type: "mixed-120",
|
|
1008
1117
|
minFrames: 120,
|
|
1009
1118
|
recommendedFrames: 120,
|
|
1010
1119
|
description: "Mixed 120-frame model",
|
|
1011
|
-
deprecated: true
|
|
1120
|
+
deprecated: true,
|
|
1121
|
+
aliases: ["v1"],
|
|
1122
|
+
sunsetDate: "2026-09-01",
|
|
1123
|
+
replacement: "mixed-120-v2"
|
|
1012
1124
|
},
|
|
1013
1125
|
"mixed-150": {
|
|
1014
1126
|
type: "mixed-150",
|
|
1015
1127
|
minFrames: 150,
|
|
1016
1128
|
recommendedFrames: 150,
|
|
1017
1129
|
description: "Mixed 150-frame model",
|
|
1018
|
-
deprecated: true
|
|
1130
|
+
deprecated: true,
|
|
1131
|
+
aliases: ["v1"],
|
|
1132
|
+
sunsetDate: "2026-09-01"
|
|
1019
1133
|
},
|
|
1020
1134
|
"mixed-250": {
|
|
1021
1135
|
type: "mixed-250",
|
|
1022
1136
|
minFrames: 250,
|
|
1023
1137
|
recommendedFrames: 250,
|
|
1024
1138
|
description: "Mixed 250-frame model",
|
|
1025
|
-
deprecated: true
|
|
1139
|
+
deprecated: true,
|
|
1140
|
+
aliases: ["v1"],
|
|
1141
|
+
sunsetDate: "2026-09-01"
|
|
1026
1142
|
},
|
|
1027
1143
|
// Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
|
|
1028
1144
|
"mixed-10-v2": {
|
|
@@ -1030,35 +1146,40 @@ var MODEL_CONFIGS = {
|
|
|
1030
1146
|
minFrames: 10,
|
|
1031
1147
|
recommendedFrames: 10,
|
|
1032
1148
|
description: "Mixed V2 10-frame model",
|
|
1033
|
-
deprecated: false
|
|
1149
|
+
deprecated: false,
|
|
1150
|
+
aliases: ["latest", "v2"]
|
|
1034
1151
|
},
|
|
1035
1152
|
"mixed-30-v2": {
|
|
1036
1153
|
type: "mixed-30-v2",
|
|
1037
1154
|
minFrames: 30,
|
|
1038
1155
|
recommendedFrames: 30,
|
|
1039
1156
|
description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
|
|
1040
|
-
deprecated: false
|
|
1157
|
+
deprecated: false,
|
|
1158
|
+
aliases: ["latest", "v2"]
|
|
1041
1159
|
},
|
|
1042
1160
|
"mixed-60-v2": {
|
|
1043
1161
|
type: "mixed-60-v2",
|
|
1044
1162
|
minFrames: 60,
|
|
1045
1163
|
recommendedFrames: 60,
|
|
1046
1164
|
description: "Mixed V2 60-frame model",
|
|
1047
|
-
deprecated: false
|
|
1165
|
+
deprecated: false,
|
|
1166
|
+
aliases: ["latest", "v2"]
|
|
1048
1167
|
},
|
|
1049
1168
|
"mixed-90-v2": {
|
|
1050
1169
|
type: "mixed-90-v2",
|
|
1051
1170
|
minFrames: 90,
|
|
1052
1171
|
recommendedFrames: 90,
|
|
1053
1172
|
description: "Mixed V2 90-frame model",
|
|
1054
|
-
deprecated: false
|
|
1173
|
+
deprecated: false,
|
|
1174
|
+
aliases: ["latest", "v2"]
|
|
1055
1175
|
},
|
|
1056
1176
|
"mixed-120-v2": {
|
|
1057
1177
|
type: "mixed-120-v2",
|
|
1058
1178
|
minFrames: 120,
|
|
1059
1179
|
recommendedFrames: 120,
|
|
1060
1180
|
description: "Mixed V2 120-frame model",
|
|
1061
|
-
deprecated: false
|
|
1181
|
+
deprecated: false,
|
|
1182
|
+
aliases: ["latest", "v2"]
|
|
1062
1183
|
}
|
|
1063
1184
|
};
|
|
1064
1185
|
var HYBRID_MODEL_CONFIGS = {
|
|
@@ -1254,9 +1375,10 @@ var FEEDBACK_MESSAGES = {
|
|
|
1254
1375
|
poor_lighting: "Improve lighting",
|
|
1255
1376
|
too_dark: "Low lighting - move to a brighter area",
|
|
1256
1377
|
backlit: "Backlit - try facing the light source",
|
|
1257
|
-
//
|
|
1258
|
-
|
|
1259
|
-
|
|
1378
|
+
// Camera angle (platform-agnostic)
|
|
1379
|
+
camera_angle_low: "Raise camera to eye level",
|
|
1380
|
+
camera_angle_high: "Lower camera to eye level",
|
|
1381
|
+
camera_tilted: "Hold camera level",
|
|
1260
1382
|
// Hand occlusion
|
|
1261
1383
|
hand_detected: "Remove hand from face",
|
|
1262
1384
|
// Eye region quality
|
|
@@ -1316,9 +1438,10 @@ var ES_LOCALE = {
|
|
|
1316
1438
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1317
1439
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1318
1440
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
|
|
1441
|
+
// Camera angle (platform-agnostic)
|
|
1442
|
+
camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
|
|
1443
|
+
camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
|
|
1444
|
+
camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
|
|
1322
1445
|
// Hand occlusion
|
|
1323
1446
|
hand_detected: "Retira la mano del rostro",
|
|
1324
1447
|
// Eye region quality
|
|
@@ -1354,17 +1477,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1354
1477
|
targetFrames,
|
|
1355
1478
|
tooFarFromIdeal,
|
|
1356
1479
|
tooCloseToIdeal,
|
|
1357
|
-
|
|
1358
|
-
|
|
1480
|
+
cameraAngleLow,
|
|
1481
|
+
cameraAngleHigh,
|
|
1482
|
+
cameraTilted
|
|
1359
1483
|
} = state;
|
|
1360
1484
|
if (!hasFace) {
|
|
1361
1485
|
return FEEDBACK_MESSAGES.no_face;
|
|
1362
1486
|
}
|
|
1363
|
-
if (
|
|
1364
|
-
return FEEDBACK_MESSAGES.
|
|
1487
|
+
if (cameraTilted) {
|
|
1488
|
+
return FEEDBACK_MESSAGES.camera_tilted;
|
|
1365
1489
|
}
|
|
1366
|
-
if (
|
|
1367
|
-
return FEEDBACK_MESSAGES.
|
|
1490
|
+
if (cameraAngleLow) {
|
|
1491
|
+
return FEEDBACK_MESSAGES.camera_angle_low;
|
|
1492
|
+
}
|
|
1493
|
+
if (cameraAngleHigh) {
|
|
1494
|
+
return FEEDBACK_MESSAGES.camera_angle_high;
|
|
1368
1495
|
}
|
|
1369
1496
|
if (tooClose) {
|
|
1370
1497
|
return FEEDBACK_MESSAGES.too_close;
|
|
@@ -1408,10 +1535,11 @@ function canCaptureFrame(state) {
|
|
|
1408
1535
|
isPartialFace,
|
|
1409
1536
|
tooFarFromIdeal,
|
|
1410
1537
|
tooCloseToIdeal,
|
|
1411
|
-
|
|
1412
|
-
|
|
1538
|
+
cameraAngleLow,
|
|
1539
|
+
cameraAngleHigh,
|
|
1540
|
+
cameraTilted
|
|
1413
1541
|
} = state;
|
|
1414
|
-
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !
|
|
1542
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
|
|
1415
1543
|
}
|
|
1416
1544
|
|
|
1417
1545
|
// src/utils/validators.ts
|
|
@@ -1478,7 +1606,7 @@ function decodeBase64(base64) {
|
|
|
1478
1606
|
}
|
|
1479
1607
|
|
|
1480
1608
|
// src/utils/frameAnalysis.ts
|
|
1481
|
-
var DEFAULT_BLUR_THRESHOLD =
|
|
1609
|
+
var DEFAULT_BLUR_THRESHOLD = 110;
|
|
1482
1610
|
var BLUR_THRESHOLD_MOBILE = 60;
|
|
1483
1611
|
var BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
1484
1612
|
var LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -1779,6 +1907,30 @@ function detectFaceRoll(landmarks) {
|
|
|
1779
1907
|
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1780
1908
|
};
|
|
1781
1909
|
}
|
|
1910
|
+
var CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
1911
|
+
var CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
1912
|
+
function detectCameraAngle(landmarks) {
|
|
1913
|
+
if (landmarks.length < 153) {
|
|
1914
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1915
|
+
}
|
|
1916
|
+
const forehead = landmarks[10];
|
|
1917
|
+
const noseTip = landmarks[1];
|
|
1918
|
+
const chin = landmarks[152];
|
|
1919
|
+
if (!forehead || !noseTip || !chin) {
|
|
1920
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1921
|
+
}
|
|
1922
|
+
const foreheadToNose = Math.abs(forehead.y - noseTip.y);
|
|
1923
|
+
const noseToChin = Math.abs(noseTip.y - chin.y);
|
|
1924
|
+
if (noseToChin < 1e-3) {
|
|
1925
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1926
|
+
}
|
|
1927
|
+
const ratio = foreheadToNose / noseToChin;
|
|
1928
|
+
return {
|
|
1929
|
+
ratio,
|
|
1930
|
+
cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
|
|
1931
|
+
cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1782
1934
|
var BaseFrameCollector = class {
|
|
1783
1935
|
constructor(maxFrames = 10) {
|
|
1784
1936
|
this.frames = [];
|
|
@@ -1996,6 +2148,8 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
1996
2148
|
BACKLIT_RATIO_THRESHOLD,
|
|
1997
2149
|
BLUR_THRESHOLD_MOBILE,
|
|
1998
2150
|
BaseFrameCollector,
|
|
2151
|
+
CAMERA_ANGLE_HIGH_RATIO,
|
|
2152
|
+
CAMERA_ANGLE_LOW_RATIO,
|
|
1999
2153
|
DEFAULT_BLUR_THRESHOLD,
|
|
2000
2154
|
DEFAULT_ENDPOINT,
|
|
2001
2155
|
DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -2048,6 +2202,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
2048
2202
|
OVAL_REGION_MOBILE,
|
|
2049
2203
|
RETRY_CONFIG,
|
|
2050
2204
|
TARGET_FACE_PERCENTAGE_IN_CROP,
|
|
2205
|
+
VALID_FRAME_COUNTS,
|
|
2051
2206
|
analyzeBlur,
|
|
2052
2207
|
analyzeEyeRegionBrightness,
|
|
2053
2208
|
analyzeEyeRegionContrast,
|
|
@@ -2060,6 +2215,7 @@ function checkEyeRegionQuality(pixels, thresholds = EYE_QUALITY_THRESHOLDS) {
|
|
|
2060
2215
|
checkEyeRegionQuality,
|
|
2061
2216
|
checkFrameQuality,
|
|
2062
2217
|
decodeBase64,
|
|
2218
|
+
detectCameraAngle,
|
|
2063
2219
|
detectFaceRoll,
|
|
2064
2220
|
detectSpecularHighlights,
|
|
2065
2221
|
encodeBase64,
|