@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.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
|
-
|
|
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.
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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,18 +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,
|
|
351
|
-
...options.
|
|
413
|
+
...options.frameCount != null ? { frame_count: options.frameCount } : {},
|
|
414
|
+
...options.warnings?.length ? { warnings: options.warnings } : {},
|
|
415
|
+
...options.bgSegmentation !== void 0 ? { bg_segmentation: options.bgSegmentation } : {}
|
|
352
416
|
};
|
|
353
|
-
const response = await this.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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;
|
|
358
433
|
}
|
|
359
434
|
/**
|
|
360
435
|
* Send a single captured frame to the streaming endpoint.
|
|
@@ -382,28 +457,29 @@ var LivenessClient = class _LivenessClient {
|
|
|
382
457
|
return this.sendStreamFrameInternal(frameData, {
|
|
383
458
|
sessionId: options.sessionId,
|
|
384
459
|
model: options.model ?? "10",
|
|
460
|
+
modelVersion: options.modelVersion,
|
|
461
|
+
frameCount: options.frameCount,
|
|
385
462
|
source: options.source ?? "live",
|
|
386
463
|
warnings: options.warnings
|
|
387
464
|
});
|
|
388
465
|
}
|
|
389
466
|
/**
|
|
390
467
|
* Send a single FrameData to the streaming endpoint with retry (internal)
|
|
391
|
-
*
|
|
392
|
-
* @param frame - Single frame to send
|
|
393
|
-
* @param options - Session and model options
|
|
394
|
-
* @returns Stream response with status
|
|
395
468
|
*/
|
|
396
469
|
async sendStreamFrameInternal(frameData, options) {
|
|
470
|
+
const effectiveVersion = options.modelVersion ?? this.modelVersion;
|
|
397
471
|
const request = {
|
|
398
472
|
session_id: options.sessionId,
|
|
399
473
|
model: options.model,
|
|
400
474
|
source: options.source,
|
|
401
475
|
frame: frameData,
|
|
476
|
+
...options.frameCount != null ? { frame_count: options.frameCount } : {},
|
|
402
477
|
...options.warnings?.length ? { warnings: options.warnings } : {}
|
|
403
478
|
};
|
|
404
479
|
return this.requestWithRetry(API_PATHS.fastCheckStream, {
|
|
405
480
|
method: "POST",
|
|
406
|
-
body: JSON.stringify(request)
|
|
481
|
+
body: JSON.stringify(request),
|
|
482
|
+
headers: this.buildModelVersionHeaders(effectiveVersion)
|
|
407
483
|
});
|
|
408
484
|
}
|
|
409
485
|
/**
|
|
@@ -428,6 +504,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
428
504
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
429
505
|
sessionId,
|
|
430
506
|
model,
|
|
507
|
+
modelVersion: options.modelVersion,
|
|
508
|
+
frameCount: options.frameCount,
|
|
431
509
|
source,
|
|
432
510
|
warnings: options.warnings
|
|
433
511
|
});
|
|
@@ -478,6 +556,8 @@ var LivenessClient = class _LivenessClient {
|
|
|
478
556
|
const response = await this.sendStreamFrameInternal(frameData, {
|
|
479
557
|
sessionId,
|
|
480
558
|
model,
|
|
559
|
+
modelVersion: options.modelVersion,
|
|
560
|
+
frameCount: options.frameCount,
|
|
481
561
|
source,
|
|
482
562
|
warnings: options.warnings
|
|
483
563
|
});
|
|
@@ -754,6 +834,7 @@ var FrameQueue = class {
|
|
|
754
834
|
};
|
|
755
835
|
|
|
756
836
|
// src/types/models.ts
|
|
837
|
+
var VALID_FRAME_COUNTS = [10, 30, 60, 90, 120];
|
|
757
838
|
var MODEL_CONFIGS = {
|
|
758
839
|
// Standard fast-check models
|
|
759
840
|
"10": {
|
|
@@ -761,21 +842,24 @@ var MODEL_CONFIGS = {
|
|
|
761
842
|
minFrames: 10,
|
|
762
843
|
recommendedFrames: 10,
|
|
763
844
|
description: "Fast model - 10 frames, quick verification",
|
|
764
|
-
deprecated: false
|
|
845
|
+
deprecated: false,
|
|
846
|
+
aliases: ["fast"]
|
|
765
847
|
},
|
|
766
848
|
"50": {
|
|
767
849
|
type: "50",
|
|
768
850
|
minFrames: 50,
|
|
769
851
|
recommendedFrames: 50,
|
|
770
852
|
description: "Balanced model - 50 frames, good accuracy",
|
|
771
|
-
deprecated: false
|
|
853
|
+
deprecated: false,
|
|
854
|
+
aliases: ["spatial"]
|
|
772
855
|
},
|
|
773
856
|
"250": {
|
|
774
857
|
type: "250",
|
|
775
858
|
minFrames: 250,
|
|
776
859
|
recommendedFrames: 250,
|
|
777
860
|
description: "High-accuracy model - 250 frames, best accuracy",
|
|
778
|
-
deprecated: false
|
|
861
|
+
deprecated: false,
|
|
862
|
+
aliases: ["spatial"]
|
|
779
863
|
},
|
|
780
864
|
// Hybrid V2 models with physiological features
|
|
781
865
|
"hybrid-v2-10": {
|
|
@@ -783,63 +867,72 @@ var MODEL_CONFIGS = {
|
|
|
783
867
|
minFrames: 10,
|
|
784
868
|
recommendedFrames: 10,
|
|
785
869
|
description: "Hybrid V2 10-frame model with physio features",
|
|
786
|
-
deprecated: false
|
|
870
|
+
deprecated: false,
|
|
871
|
+
aliases: ["hybrid"]
|
|
787
872
|
},
|
|
788
873
|
"hybrid-v2-30": {
|
|
789
874
|
type: "hybrid-v2-30",
|
|
790
875
|
minFrames: 30,
|
|
791
876
|
recommendedFrames: 30,
|
|
792
877
|
description: "Hybrid V2 30-frame model with physio features",
|
|
793
|
-
deprecated: false
|
|
878
|
+
deprecated: false,
|
|
879
|
+
aliases: ["hybrid"]
|
|
794
880
|
},
|
|
795
881
|
"hybrid-v2-50": {
|
|
796
882
|
type: "hybrid-v2-50",
|
|
797
883
|
minFrames: 50,
|
|
798
884
|
recommendedFrames: 50,
|
|
799
885
|
description: "Hybrid V2 50-frame model with physio features",
|
|
800
|
-
deprecated: false
|
|
886
|
+
deprecated: false,
|
|
887
|
+
aliases: ["hybrid"]
|
|
801
888
|
},
|
|
802
889
|
"hybrid-v2-60": {
|
|
803
890
|
type: "hybrid-v2-60",
|
|
804
891
|
minFrames: 60,
|
|
805
892
|
recommendedFrames: 60,
|
|
806
893
|
description: "Hybrid V2 60-frame model with physio features",
|
|
807
|
-
deprecated: false
|
|
894
|
+
deprecated: false,
|
|
895
|
+
aliases: ["hybrid"]
|
|
808
896
|
},
|
|
809
897
|
"hybrid-v2-90": {
|
|
810
898
|
type: "hybrid-v2-90",
|
|
811
899
|
minFrames: 90,
|
|
812
900
|
recommendedFrames: 90,
|
|
813
901
|
description: "Hybrid V2 90-frame model with physio features",
|
|
814
|
-
deprecated: false
|
|
902
|
+
deprecated: false,
|
|
903
|
+
aliases: ["hybrid"]
|
|
815
904
|
},
|
|
816
905
|
"hybrid-v2-100": {
|
|
817
906
|
type: "hybrid-v2-100",
|
|
818
907
|
minFrames: 100,
|
|
819
908
|
recommendedFrames: 100,
|
|
820
909
|
description: "Hybrid V2 100-frame model with physio features",
|
|
821
|
-
deprecated: false
|
|
910
|
+
deprecated: false,
|
|
911
|
+
aliases: ["hybrid"]
|
|
822
912
|
},
|
|
823
913
|
"hybrid-v2-125": {
|
|
824
914
|
type: "hybrid-v2-125",
|
|
825
915
|
minFrames: 125,
|
|
826
916
|
recommendedFrames: 125,
|
|
827
917
|
description: "Hybrid V2 125-frame model with physio features",
|
|
828
|
-
deprecated: false
|
|
918
|
+
deprecated: false,
|
|
919
|
+
aliases: ["hybrid"]
|
|
829
920
|
},
|
|
830
921
|
"hybrid-v2-150": {
|
|
831
922
|
type: "hybrid-v2-150",
|
|
832
923
|
minFrames: 150,
|
|
833
924
|
recommendedFrames: 150,
|
|
834
925
|
description: "Hybrid V2 150-frame model with physio features",
|
|
835
|
-
deprecated: false
|
|
926
|
+
deprecated: false,
|
|
927
|
+
aliases: ["hybrid"]
|
|
836
928
|
},
|
|
837
929
|
"hybrid-v2-250": {
|
|
838
930
|
type: "hybrid-v2-250",
|
|
839
931
|
minFrames: 250,
|
|
840
932
|
recommendedFrames: 250,
|
|
841
933
|
description: "Hybrid V2 250-frame model with physio features",
|
|
842
|
-
deprecated: false
|
|
934
|
+
deprecated: false,
|
|
935
|
+
aliases: ["hybrid"]
|
|
843
936
|
},
|
|
844
937
|
// Mixed V1 models — deprecated, use mixed-*-v2 equivalents instead
|
|
845
938
|
"mixed-10": {
|
|
@@ -847,49 +940,68 @@ var MODEL_CONFIGS = {
|
|
|
847
940
|
minFrames: 10,
|
|
848
941
|
recommendedFrames: 10,
|
|
849
942
|
description: "Mixed 10-frame model",
|
|
850
|
-
deprecated: true
|
|
943
|
+
deprecated: true,
|
|
944
|
+
aliases: ["v1"],
|
|
945
|
+
sunsetDate: "2026-09-01",
|
|
946
|
+
replacement: "mixed-10-v2"
|
|
851
947
|
},
|
|
852
948
|
"mixed-30": {
|
|
853
949
|
type: "mixed-30",
|
|
854
950
|
minFrames: 30,
|
|
855
951
|
recommendedFrames: 30,
|
|
856
952
|
description: "Mixed 30-frame model",
|
|
857
|
-
deprecated: true
|
|
953
|
+
deprecated: true,
|
|
954
|
+
aliases: ["v1"],
|
|
955
|
+
sunsetDate: "2026-09-01",
|
|
956
|
+
replacement: "mixed-30-v2"
|
|
858
957
|
},
|
|
859
958
|
"mixed-60": {
|
|
860
959
|
type: "mixed-60",
|
|
861
960
|
minFrames: 60,
|
|
862
961
|
recommendedFrames: 60,
|
|
863
962
|
description: "Mixed 60-frame model",
|
|
864
|
-
deprecated: true
|
|
963
|
+
deprecated: true,
|
|
964
|
+
aliases: ["v1"],
|
|
965
|
+
sunsetDate: "2026-09-01",
|
|
966
|
+
replacement: "mixed-60-v2"
|
|
865
967
|
},
|
|
866
968
|
"mixed-90": {
|
|
867
969
|
type: "mixed-90",
|
|
868
970
|
minFrames: 90,
|
|
869
971
|
recommendedFrames: 90,
|
|
870
972
|
description: "Mixed 90-frame model",
|
|
871
|
-
deprecated: true
|
|
973
|
+
deprecated: true,
|
|
974
|
+
aliases: ["v1"],
|
|
975
|
+
sunsetDate: "2026-09-01",
|
|
976
|
+
replacement: "mixed-90-v2"
|
|
872
977
|
},
|
|
873
978
|
"mixed-120": {
|
|
874
979
|
type: "mixed-120",
|
|
875
980
|
minFrames: 120,
|
|
876
981
|
recommendedFrames: 120,
|
|
877
982
|
description: "Mixed 120-frame model",
|
|
878
|
-
deprecated: true
|
|
983
|
+
deprecated: true,
|
|
984
|
+
aliases: ["v1"],
|
|
985
|
+
sunsetDate: "2026-09-01",
|
|
986
|
+
replacement: "mixed-120-v2"
|
|
879
987
|
},
|
|
880
988
|
"mixed-150": {
|
|
881
989
|
type: "mixed-150",
|
|
882
990
|
minFrames: 150,
|
|
883
991
|
recommendedFrames: 150,
|
|
884
992
|
description: "Mixed 150-frame model",
|
|
885
|
-
deprecated: true
|
|
993
|
+
deprecated: true,
|
|
994
|
+
aliases: ["v1"],
|
|
995
|
+
sunsetDate: "2026-09-01"
|
|
886
996
|
},
|
|
887
997
|
"mixed-250": {
|
|
888
998
|
type: "mixed-250",
|
|
889
999
|
minFrames: 250,
|
|
890
1000
|
recommendedFrames: 250,
|
|
891
1001
|
description: "Mixed 250-frame model",
|
|
892
|
-
deprecated: true
|
|
1002
|
+
deprecated: true,
|
|
1003
|
+
aliases: ["v1"],
|
|
1004
|
+
sunsetDate: "2026-09-01"
|
|
893
1005
|
},
|
|
894
1006
|
// Mixed V2 models (exp_042 checkpoints — EER: 4.0% / AUC: 0.991 at 30f)
|
|
895
1007
|
"mixed-10-v2": {
|
|
@@ -897,35 +1009,40 @@ var MODEL_CONFIGS = {
|
|
|
897
1009
|
minFrames: 10,
|
|
898
1010
|
recommendedFrames: 10,
|
|
899
1011
|
description: "Mixed V2 10-frame model",
|
|
900
|
-
deprecated: false
|
|
1012
|
+
deprecated: false,
|
|
1013
|
+
aliases: ["latest", "v2"]
|
|
901
1014
|
},
|
|
902
1015
|
"mixed-30-v2": {
|
|
903
1016
|
type: "mixed-30-v2",
|
|
904
1017
|
minFrames: 30,
|
|
905
1018
|
recommendedFrames: 30,
|
|
906
1019
|
description: "Mixed V2 30-frame model (recommended \u2014 95.7% balanced accuracy)",
|
|
907
|
-
deprecated: false
|
|
1020
|
+
deprecated: false,
|
|
1021
|
+
aliases: ["latest", "v2"]
|
|
908
1022
|
},
|
|
909
1023
|
"mixed-60-v2": {
|
|
910
1024
|
type: "mixed-60-v2",
|
|
911
1025
|
minFrames: 60,
|
|
912
1026
|
recommendedFrames: 60,
|
|
913
1027
|
description: "Mixed V2 60-frame model",
|
|
914
|
-
deprecated: false
|
|
1028
|
+
deprecated: false,
|
|
1029
|
+
aliases: ["latest", "v2"]
|
|
915
1030
|
},
|
|
916
1031
|
"mixed-90-v2": {
|
|
917
1032
|
type: "mixed-90-v2",
|
|
918
1033
|
minFrames: 90,
|
|
919
1034
|
recommendedFrames: 90,
|
|
920
1035
|
description: "Mixed V2 90-frame model",
|
|
921
|
-
deprecated: false
|
|
1036
|
+
deprecated: false,
|
|
1037
|
+
aliases: ["latest", "v2"]
|
|
922
1038
|
},
|
|
923
1039
|
"mixed-120-v2": {
|
|
924
1040
|
type: "mixed-120-v2",
|
|
925
1041
|
minFrames: 120,
|
|
926
1042
|
recommendedFrames: 120,
|
|
927
1043
|
description: "Mixed V2 120-frame model",
|
|
928
|
-
deprecated: false
|
|
1044
|
+
deprecated: false,
|
|
1045
|
+
aliases: ["latest", "v2"]
|
|
929
1046
|
}
|
|
930
1047
|
};
|
|
931
1048
|
var HYBRID_MODEL_CONFIGS = {
|
|
@@ -1121,9 +1238,10 @@ var FEEDBACK_MESSAGES = {
|
|
|
1121
1238
|
poor_lighting: "Improve lighting",
|
|
1122
1239
|
too_dark: "Low lighting - move to a brighter area",
|
|
1123
1240
|
backlit: "Backlit - try facing the light source",
|
|
1124
|
-
//
|
|
1125
|
-
|
|
1126
|
-
|
|
1241
|
+
// Camera angle (platform-agnostic)
|
|
1242
|
+
camera_angle_low: "Raise camera to eye level",
|
|
1243
|
+
camera_angle_high: "Lower camera to eye level",
|
|
1244
|
+
camera_tilted: "Hold camera level",
|
|
1127
1245
|
// Hand occlusion
|
|
1128
1246
|
hand_detected: "Remove hand from face",
|
|
1129
1247
|
// Eye region quality
|
|
@@ -1183,9 +1301,10 @@ var ES_LOCALE = {
|
|
|
1183
1301
|
poor_lighting: "Mejora la iluminaci\xF3n",
|
|
1184
1302
|
too_dark: "Poca luz - mu\xE9vete a un \xE1rea m\xE1s iluminada",
|
|
1185
1303
|
backlit: "Contraluz - intenta mirar hacia la fuente de luz",
|
|
1186
|
-
//
|
|
1187
|
-
|
|
1188
|
-
|
|
1304
|
+
// Camera angle (platform-agnostic)
|
|
1305
|
+
camera_angle_low: "Levanta la c\xE1mara a la altura de los ojos",
|
|
1306
|
+
camera_angle_high: "Baja la c\xE1mara a la altura de los ojos",
|
|
1307
|
+
camera_tilted: "Mant\xE9n la c\xE1mara nivelada",
|
|
1189
1308
|
// Hand occlusion
|
|
1190
1309
|
hand_detected: "Retira la mano del rostro",
|
|
1191
1310
|
// Eye region quality
|
|
@@ -1221,17 +1340,21 @@ function getCaptureQualityFeedback(state) {
|
|
|
1221
1340
|
targetFrames,
|
|
1222
1341
|
tooFarFromIdeal,
|
|
1223
1342
|
tooCloseToIdeal,
|
|
1224
|
-
|
|
1225
|
-
|
|
1343
|
+
cameraAngleLow,
|
|
1344
|
+
cameraAngleHigh,
|
|
1345
|
+
cameraTilted
|
|
1226
1346
|
} = state;
|
|
1227
1347
|
if (!hasFace) {
|
|
1228
1348
|
return FEEDBACK_MESSAGES.no_face;
|
|
1229
1349
|
}
|
|
1230
|
-
if (
|
|
1231
|
-
return FEEDBACK_MESSAGES.
|
|
1350
|
+
if (cameraTilted) {
|
|
1351
|
+
return FEEDBACK_MESSAGES.camera_tilted;
|
|
1232
1352
|
}
|
|
1233
|
-
if (
|
|
1234
|
-
return FEEDBACK_MESSAGES.
|
|
1353
|
+
if (cameraAngleLow) {
|
|
1354
|
+
return FEEDBACK_MESSAGES.camera_angle_low;
|
|
1355
|
+
}
|
|
1356
|
+
if (cameraAngleHigh) {
|
|
1357
|
+
return FEEDBACK_MESSAGES.camera_angle_high;
|
|
1235
1358
|
}
|
|
1236
1359
|
if (tooClose) {
|
|
1237
1360
|
return FEEDBACK_MESSAGES.too_close;
|
|
@@ -1275,10 +1398,11 @@ function canCaptureFrame(state) {
|
|
|
1275
1398
|
isPartialFace,
|
|
1276
1399
|
tooFarFromIdeal,
|
|
1277
1400
|
tooCloseToIdeal,
|
|
1278
|
-
|
|
1279
|
-
|
|
1401
|
+
cameraAngleLow,
|
|
1402
|
+
cameraAngleHigh,
|
|
1403
|
+
cameraTilted
|
|
1280
1404
|
} = state;
|
|
1281
|
-
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !
|
|
1405
|
+
return hasFace && alignment >= ALIGNMENT_THRESHOLD_CAPTURE && !tooClose && !tooFar && !isBlurry && !isPartialFace && !tooFarFromIdeal && !tooCloseToIdeal && !cameraAngleLow && !cameraAngleHigh && !cameraTilted;
|
|
1282
1406
|
}
|
|
1283
1407
|
|
|
1284
1408
|
// src/utils/validators.ts
|
|
@@ -1345,7 +1469,7 @@ function decodeBase64(base64) {
|
|
|
1345
1469
|
}
|
|
1346
1470
|
|
|
1347
1471
|
// src/utils/frameAnalysis.ts
|
|
1348
|
-
var DEFAULT_BLUR_THRESHOLD =
|
|
1472
|
+
var DEFAULT_BLUR_THRESHOLD = 110;
|
|
1349
1473
|
var BLUR_THRESHOLD_MOBILE = 60;
|
|
1350
1474
|
var BACKLIT_RATIO_THRESHOLD = 0.6;
|
|
1351
1475
|
var LOW_LIGHT_THRESHOLD = 50;
|
|
@@ -1646,6 +1770,30 @@ function detectFaceRoll(landmarks) {
|
|
|
1646
1770
|
tooTilted: roll > MAX_FACE_ROLL_DEGREES
|
|
1647
1771
|
};
|
|
1648
1772
|
}
|
|
1773
|
+
var CAMERA_ANGLE_HIGH_RATIO = 1.35;
|
|
1774
|
+
var CAMERA_ANGLE_LOW_RATIO = 0.75;
|
|
1775
|
+
function detectCameraAngle(landmarks) {
|
|
1776
|
+
if (landmarks.length < 153) {
|
|
1777
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1778
|
+
}
|
|
1779
|
+
const forehead = landmarks[10];
|
|
1780
|
+
const noseTip = landmarks[1];
|
|
1781
|
+
const chin = landmarks[152];
|
|
1782
|
+
if (!forehead || !noseTip || !chin) {
|
|
1783
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1784
|
+
}
|
|
1785
|
+
const foreheadToNose = Math.abs(forehead.y - noseTip.y);
|
|
1786
|
+
const noseToChin = Math.abs(noseTip.y - chin.y);
|
|
1787
|
+
if (noseToChin < 1e-3) {
|
|
1788
|
+
return { ratio: 1, cameraAbove: false, cameraBelow: false };
|
|
1789
|
+
}
|
|
1790
|
+
const ratio = foreheadToNose / noseToChin;
|
|
1791
|
+
return {
|
|
1792
|
+
ratio,
|
|
1793
|
+
cameraAbove: ratio > CAMERA_ANGLE_HIGH_RATIO,
|
|
1794
|
+
cameraBelow: ratio < CAMERA_ANGLE_LOW_RATIO
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1649
1797
|
var BaseFrameCollector = class {
|
|
1650
1798
|
constructor(maxFrames = 10) {
|
|
1651
1799
|
this.frames = [];
|
|
@@ -1862,6 +2010,8 @@ export {
|
|
|
1862
2010
|
BACKLIT_RATIO_THRESHOLD,
|
|
1863
2011
|
BLUR_THRESHOLD_MOBILE,
|
|
1864
2012
|
BaseFrameCollector,
|
|
2013
|
+
CAMERA_ANGLE_HIGH_RATIO,
|
|
2014
|
+
CAMERA_ANGLE_LOW_RATIO,
|
|
1865
2015
|
DEFAULT_BLUR_THRESHOLD,
|
|
1866
2016
|
DEFAULT_ENDPOINT,
|
|
1867
2017
|
DEFAULT_FACE_DETECTION_TIERS,
|
|
@@ -1914,6 +2064,7 @@ export {
|
|
|
1914
2064
|
OVAL_REGION_MOBILE,
|
|
1915
2065
|
RETRY_CONFIG,
|
|
1916
2066
|
TARGET_FACE_PERCENTAGE_IN_CROP,
|
|
2067
|
+
VALID_FRAME_COUNTS,
|
|
1917
2068
|
analyzeBlur,
|
|
1918
2069
|
analyzeEyeRegionBrightness,
|
|
1919
2070
|
analyzeEyeRegionContrast,
|
|
@@ -1926,6 +2077,7 @@ export {
|
|
|
1926
2077
|
checkEyeRegionQuality,
|
|
1927
2078
|
checkFrameQuality,
|
|
1928
2079
|
decodeBase64,
|
|
2080
|
+
detectCameraAngle,
|
|
1929
2081
|
detectFaceRoll,
|
|
1930
2082
|
detectSpecularHighlights,
|
|
1931
2083
|
encodeBase64,
|