@sssxyd/face-liveness-detector 0.2.32 → 0.2.34

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
@@ -336,7 +336,13 @@
336
336
  if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
337
337
  return 'gecko';
338
338
  }
339
- // 2. 检测 WebKit
339
+ // 2. 检测 Chromium/Blink(必须在 WebKit 之前,因为 Chrome 的 user-agent 也包含 WebKit)
340
+ // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
341
+ if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
342
+ return 'chromium';
343
+ }
344
+ // 3. 检测 WebKit(真正的 Safari 和 iOS 浏览器)
345
+ // 注意:真正的 WebKit 浏览器(Safari)user-agent 不包含 Chrome 标识
340
346
  // 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
341
347
  if (/webkit/i.test(ua)) {
342
348
  // WebKit 特征明显,包括以下几种情况:
@@ -345,11 +351,6 @@
345
351
  // - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
346
352
  return 'webkit';
347
353
  }
348
- // 3. 检测 Chromium/Blink
349
- // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
350
- if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
351
- return 'chromium';
352
- }
353
354
  // 4. 其他浏览器 - 保守方案,使用 WASM
354
355
  return 'other';
355
356
  }
@@ -589,16 +590,11 @@
589
590
  return null;
590
591
  }
591
592
  /**
592
- * Load Human.js
593
- * @param modelPath - Path to model files (optional)
594
- * @param wasmPath - Path to WASM files (optional)
595
- * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
596
- * @returns Promise that resolves with Human instance
593
+ * Create Human.js configuration object
597
594
  */
598
- async function loadHuman(modelPath, wasmPath, preferredBackend) {
599
- const selectedBackend = _detectOptimalBackend(preferredBackend);
595
+ function _createHumanConfig(backend, modelPath, wasmPath) {
600
596
  const config = {
601
- backend: selectedBackend,
597
+ backend,
602
598
  face: {
603
599
  enabled: true,
604
600
  detector: { rotation: false, return: true },
@@ -612,22 +608,80 @@
612
608
  object: { enabled: false },
613
609
  gesture: { enabled: true }
614
610
  };
615
- // 只在提供了路径时才设置,否则让 Human.js 使用默认加载策略
616
611
  if (modelPath) {
617
612
  config.modelBasePath = modelPath;
618
613
  }
619
614
  if (wasmPath) {
620
615
  config.wasmPath = wasmPath;
621
616
  }
622
- console.log('[FaceDetectionEngine] Human.js config:', {
623
- backend: config.backend,
624
- modelBasePath: config.modelBasePath || '(using default)',
625
- wasmPath: config.wasmPath || '(using default)',
626
- userAgent: navigator.userAgent,
627
- platform: navigator.platform
617
+ return config;
618
+ }
619
+ /**
620
+ * Load and verify Human.js models
621
+ */
622
+ async function _loadAndVerifyHuman(human) {
623
+ const modelLoadStartTime = performance.now();
624
+ try {
625
+ await human.load();
626
+ }
627
+ catch (error) {
628
+ const errorMsg = error instanceof Error ? error.message : 'Unknown error';
629
+ const errorStack = error instanceof Error ? error.stack : 'N/A';
630
+ console.error('[FaceDetectionEngine] Error during human.load():', {
631
+ errorMsg,
632
+ stack: errorStack,
633
+ backend: human.config?.backend,
634
+ hasModels: !!human.models,
635
+ modelsKeys: human.models ? Object.keys(human.models).length : 0
636
+ });
637
+ throw new Error(`Model loading error: ${errorMsg}`);
638
+ }
639
+ const loadTime = performance.now() - modelLoadStartTime;
640
+ console.log('[FaceDetectionEngine] Human.js loaded successfully', {
641
+ modelLoadTime: `${loadTime.toFixed(2)}ms`,
642
+ version: human.version,
643
+ config: human.config
628
644
  });
645
+ // 验证加载后的 Human 实例有必要的方法和属性
646
+ if (typeof human.detect !== 'function') {
647
+ throw new Error('Human.detect method not available after loading');
648
+ }
649
+ if (!human.version) {
650
+ console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
651
+ }
652
+ // 关键验证:检查模型是否真的加载了
653
+ if (!human.models || Object.keys(human.models).length === 0) {
654
+ console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
655
+ throw new Error('No models were loaded - human.models is empty');
656
+ }
657
+ // 打印加载的模型信息
658
+ if (human.models) {
659
+ const loadedModels = Object.entries(human.models).map(([name, model]) => ({
660
+ name,
661
+ loaded: model?.loaded || model?.state === 'loaded',
662
+ type: typeof model,
663
+ hasModel: !!model?.model,
664
+ keys: Object.keys(model).length
665
+ }));
666
+ console.log('[FaceDetectionEngine] All loaded models:', {
667
+ backend: human.config?.backend,
668
+ modelBasePath: human.config?.modelBasePath,
669
+ wasmPath: human.config?.wasmPath,
670
+ totalModels: Object.keys(human.models).length,
671
+ models: loadedModels,
672
+ allModelNames: Object.keys(human.models)
673
+ });
674
+ }
675
+ }
676
+ /**
677
+ * Try to load Human with a specific backend
678
+ * @param config The configuration object
679
+ * @param backend The backend to try
680
+ * @returns Human instance or null if fails
681
+ */
682
+ async function _tryLoadHumanWithBackend(backend, modelPath, wasmPath) {
683
+ const config = _createHumanConfig(backend, modelPath, wasmPath);
629
684
  const initStartTime = performance.now();
630
- console.log('[FaceDetectionEngine] Creating Human instance...');
631
685
  let human;
632
686
  try {
633
687
  human = new Human(config);
@@ -635,164 +689,74 @@
635
689
  catch (error) {
636
690
  const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
637
691
  const stack = error instanceof Error ? error.stack : 'N/A';
638
- console.error('[FaceDetectionEngine] Failed to create Human instance:', {
692
+ console.error(`[FaceDetectionEngine] Failed to create Human instance (${backend}):`, {
639
693
  errorMsg,
640
694
  stack,
641
695
  backend: config.backend,
642
696
  userAgent: navigator.userAgent
643
697
  });
644
- throw new Error(`Human instantiation failed: ${errorMsg}`);
698
+ return null;
645
699
  }
646
- const instanceCreateTime = performance.now() - initStartTime;
647
- console.log(`[FaceDetectionEngine] Human instance created, took ${instanceCreateTime.toFixed(2)}ms`);
648
700
  // 验证 Human 实例
649
701
  if (!human) {
650
- throw new Error('Human instance is null after creation');
702
+ console.error(`[FaceDetectionEngine] Human instance is null (${backend})`);
703
+ return null;
651
704
  }
652
705
  // 验证 Human 实例结构(早期检测 WASM 问题)
653
706
  if (!human.config) {
654
707
  console.warn('[FaceDetectionEngine] Warning: human.config is missing');
655
708
  }
656
- console.log('[FaceDetectionEngine] Loading Human.js models...');
657
- const modelLoadStartTime = performance.now();
658
709
  try {
659
- // 添加超时机制防止无限等待
660
- const loadTimeout = new Promise((_, reject) => {
661
- const timeoutId = setTimeout(() => {
662
- reject(new Error('Human.js load() timeout after 60 seconds - possible issue with model loading on mobile'));
663
- }, 60000);
664
- });
665
- // 竞速:哪个先完成就用哪个
666
- try {
667
- await Promise.race([
668
- human.load(),
669
- loadTimeout
670
- ]);
671
- }
672
- catch (raceError) {
673
- // 如果是超时错误,直接抛出
674
- if (raceError instanceof Error && raceError.message.includes('timeout')) {
675
- throw raceError;
676
- }
677
- // 其他错误,提供更多上下文
678
- const raceErrorMsg = raceError instanceof Error ? raceError.message : 'Unknown error';
679
- const raceErrorStack = raceError instanceof Error ? raceError.stack : 'N/A';
680
- console.error('[FaceDetectionEngine] Error during human.load():', {
681
- errorMsg: raceErrorMsg,
682
- stack: raceErrorStack,
683
- backend: config.backend,
684
- hasModels: !!human.models,
685
- modelsKeys: human.models ? Object.keys(human.models).length : 0
686
- });
687
- throw new Error(`Model loading error: ${raceErrorMsg}`);
688
- }
689
- const loadTime = performance.now() - modelLoadStartTime;
710
+ await _loadAndVerifyHuman(human);
690
711
  const totalTime = performance.now() - initStartTime;
691
- console.log('[FaceDetectionEngine] Human.js loaded successfully', {
692
- modelLoadTime: `${loadTime.toFixed(2)}ms`,
693
- totalInitTime: `${totalTime.toFixed(2)}ms`,
694
- version: human.version,
695
- config: human.config
696
- });
697
- // 验证加载后的 Human 实例有必要的方法和属性
698
- if (typeof human.detect !== 'function') {
699
- throw new Error('Human.detect method not available after loading');
700
- }
701
- if (!human.version) {
702
- console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
703
- }
704
- // 关键验证:检查模型是否真的加载了
705
- if (!human.models || Object.keys(human.models).length === 0) {
706
- console.error('[FaceDetectionEngine] CRITICAL: human.models is empty after loading!');
707
- throw new Error('No models were loaded - human.models is empty');
708
- }
709
- // 详细检查每个关键模型及其结构
710
- const criticalModels = ['face', 'antispoof', 'liveness'];
711
- const missingModels = [];
712
- for (const modelName of criticalModels) {
713
- const model = human.models[modelName];
714
- if (!model) {
715
- missingModels.push(modelName);
716
- console.error(`[FaceDetectionEngine] CRITICAL: Model '${modelName}' is missing!`);
717
- }
718
- else {
719
- const isLoaded = model.loaded || model.state === 'loaded' || !!model.model;
720
- // 检查模型是否有必要的内部结构(防止 "Cannot read properties of undefined (reading 'inputs')" 错误)
721
- const hasExecutor = !!model['executor'];
722
- const hasInputs = !!model.inputs && Array.isArray(model.inputs) && model.inputs.length > 0;
723
- const hasModelUrl = !!model['modelUrl'];
724
- console.log(`[FaceDetectionEngine] Model '${modelName}':`, {
725
- loaded: isLoaded,
726
- state: model.state,
727
- hasModel: !!model.model,
728
- hasExecutor,
729
- hasInputs,
730
- hasModelUrl,
731
- inputsType: typeof model.inputs,
732
- inputsLength: Array.isArray(model.inputs) ? model.inputs.length : 'N/A'
733
- });
734
- // 严格检查:模型必须有以下结构才能正常工作
735
- if (!isLoaded || !hasExecutor || !hasModelUrl) {
736
- missingModels.push(`${modelName} (incomplete)`);
737
- console.error(`[FaceDetectionEngine] WARNING: Model '${modelName}' may not be fully loaded - missing structure`);
738
- }
739
- // 如果 inputs 未定义会导致 "Cannot read properties of undefined (reading 'inputs')" 错误
740
- if (!hasInputs && modelName !== 'antispoof') {
741
- console.warn(`[FaceDetectionEngine] WARNING: Model '${modelName}' has no inputs - may cause errors during detection`);
742
- missingModels.push(`${modelName} (no inputs)`);
743
- }
744
- }
745
- }
746
- if (missingModels.length > 0) {
747
- console.error('[FaceDetectionEngine] Some critical models failed to load:', missingModels);
748
- throw new Error(`Critical models not loaded: ${missingModels.join(', ')}`);
749
- }
750
- // 打印加载的模型信息
751
- if (human.models) {
752
- const loadedModels = Object.entries(human.models).map(([name, model]) => ({
753
- name,
754
- loaded: model?.loaded || model?.state === 'loaded',
755
- type: typeof model,
756
- hasModel: !!model?.model
757
- }));
758
- console.log('[FaceDetectionEngine] All loaded models:', {
759
- totalModels: Object.keys(human.models).length,
760
- models: loadedModels,
761
- allModelNames: Object.keys(human.models)
762
- });
763
- }
712
+ console.log(`[FaceDetectionEngine] Successfully loaded Human.js with ${backend} backend in ${totalTime.toFixed(2)}ms`);
764
713
  return human;
765
714
  }
766
715
  catch (error) {
767
716
  const errorMsg = error instanceof Error ? error.message : 'Unknown error';
768
- const stack = error instanceof Error ? error.stack : 'N/A';
769
- console.error('[FaceDetectionEngine] Human.js load failed:', {
770
- errorMsg,
771
- stack,
772
- userAgent: navigator.userAgent,
773
- platform: navigator.platform,
774
- humanVersion: human?.version,
775
- humanConfig: human?.config,
776
- backend: config.backend
777
- });
778
- // 如果是 WASM 后端失败,尝试降级到 WebGL
779
- if (selectedBackend === 'wasm' && !errorMsg.includes('timeout') && _isWebGLAvailable()) {
780
- console.warn('[FaceDetectionEngine] WASM backend failed, attempting fallback to WebGL...');
781
- config.backend = 'webgl';
782
- try {
783
- console.log('[FaceDetectionEngine] Retrying with WebGL backend');
784
- human = new Human(config);
785
- await human.load();
786
- console.log('[FaceDetectionEngine] Successfully loaded with WebGL fallback');
787
- return human;
788
- }
789
- catch (fallbackError) {
790
- const fallbackMsg = fallbackError instanceof Error ? fallbackError.message : 'Unknown error';
791
- console.error('[FaceDetectionEngine] WebGL fallback also failed:', fallbackMsg);
792
- }
717
+ console.error(`[FaceDetectionEngine] Failed to load models with ${backend} backend:`, errorMsg);
718
+ return null;
719
+ }
720
+ }
721
+ /**
722
+ * Load Human.js
723
+ * @param modelPath - Path to model files (optional)
724
+ * @param wasmPath - Path to WASM files (optional)
725
+ * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
726
+ * @returns Promise that resolves with Human instance
727
+ */
728
+ async function loadHuman(modelPath, wasmPath, preferredBackend) {
729
+ const selectedBackend = _detectOptimalBackend(preferredBackend);
730
+ console.log('[FaceDetectionEngine] Starting Human.js initialization:', {
731
+ selectedBackend,
732
+ modelBasePath: modelPath || '(using default)',
733
+ wasmPath: wasmPath || '(using default)',
734
+ userAgent: navigator.userAgent,
735
+ platform: navigator.platform
736
+ });
737
+ // 尝试用主后端加载
738
+ const human = await _tryLoadHumanWithBackend(selectedBackend, modelPath, wasmPath);
739
+ if (human) {
740
+ return human;
741
+ }
742
+ console.log(`[FaceDetectionEngine] Human.js loading failed with ${selectedBackend} backend.`);
743
+ // 尝试用备选后端加载(最多一次降级)
744
+ let fallbackBackend;
745
+ if (selectedBackend === 'wasm' && _isWebGLAvailable()) {
746
+ fallbackBackend = 'webgl';
747
+ }
748
+ else if (selectedBackend === 'webgl') {
749
+ fallbackBackend = 'wasm';
750
+ }
751
+ if (fallbackBackend) {
752
+ console.warn(`[FaceDetectionEngine] Primary backend (${selectedBackend}) failed, attempting fallback to ${fallbackBackend}...`);
753
+ const humanFallback = await _tryLoadHumanWithBackend(fallbackBackend, modelPath, wasmPath);
754
+ if (humanFallback) {
755
+ return humanFallback;
793
756
  }
794
- throw new Error(`Human.js loading failed: ${errorMsg}`);
757
+ throw new Error(`Human.js loading failed: both ${selectedBackend} and ${fallbackBackend} backends failed`);
795
758
  }
759
+ throw new Error(`Human.js loading failed: ${selectedBackend} backend failed (no fallback available)`);
796
760
  }
797
761
  /**
798
762
  * Extract OpenCV version from getBuildInformation