@sssxyd/face-liveness-detector 0.2.31 → 0.2.33

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
@@ -332,21 +332,26 @@
332
332
  }
333
333
  function _detectBrowserEngine(userAgent) {
334
334
  const ua = userAgent.toLowerCase();
335
- // 检测 Gecko (Firefox)
335
+ // 1. 检测 Gecko (Firefox)
336
336
  if (/firefox/i.test(ua) && !/seamonkey/i.test(ua)) {
337
337
  return 'gecko';
338
338
  }
339
- // 检测 WebKit (Safari, iOS browsers)
340
- // Safari 的特征:有 Safari 但没有 Chrome
341
- if (/safari/i.test(ua) && !/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
342
- return 'webkit';
343
- }
344
- // 检测 Chromium/Blink
339
+ // 2. 检测 Chromium/Blink(必须在 WebKit 之前,因为 Chrome 的 user-agent 也包含 WebKit)
345
340
  // Chrome-based browsers: Chrome, Chromium, Edge, Brave, Opera, Vivaldi, Whale, Arc, etc.
346
341
  if (/chrome|chromium|crios|edge|edgios|edg|brave|opera|vivaldi|whale|arc|yabrowser|samsung|kiwi|ghostery/i.test(ua)) {
347
342
  return 'chromium';
348
343
  }
349
- // 默认为 other
344
+ // 3. 检测 WebKit(真正的 Safari 和 iOS 浏览器)
345
+ // 注意:真正的 WebKit 浏览器(Safari)user-agent 不包含 Chrome 标识
346
+ // 包括:Safari、iOS 浏览器、以及那些虽然包含 Chrome 标识但实际是 WebKit 的浏览器(Quark、支付宝、微信等)
347
+ if (/webkit/i.test(ua)) {
348
+ // WebKit 特征明显,包括以下几种情况:
349
+ // - 真正的 Safari(有 Safari 标识)
350
+ // - iOS 浏览器(有 Mobile Safari 标识)
351
+ // - Quark、支付宝、微信等虽然包含 Chrome 标识但是基于 WebKit 的浏览器
352
+ return 'webkit';
353
+ }
354
+ // 4. 其他浏览器 - 保守方案,使用 WASM
350
355
  return 'other';
351
356
  }
352
357
  function _getOptimalBackendForEngine(engine) {
@@ -585,15 +590,11 @@
585
590
  return null;
586
591
  }
587
592
  /**
588
- * Load Human.js
589
- * @param modelPath - Path to model files (optional)
590
- * @param wasmPath - Path to WASM files (optional)
591
- * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
592
- * @returns Promise that resolves with Human instance
593
+ * Create Human.js configuration object
593
594
  */
594
- async function loadHuman(modelPath, wasmPath, preferredBackend) {
595
+ function _createHumanConfig(backend, modelPath, wasmPath) {
595
596
  const config = {
596
- backend: _detectOptimalBackend(preferredBackend),
597
+ backend,
597
598
  face: {
598
599
  enabled: true,
599
600
  detector: { rotation: false, return: true },
@@ -607,102 +608,196 @@
607
608
  object: { enabled: false },
608
609
  gesture: { enabled: true }
609
610
  };
610
- // 只在提供了路径时才设置,否则让 Human.js 使用默认加载策略
611
611
  if (modelPath) {
612
612
  config.modelBasePath = modelPath;
613
613
  }
614
614
  if (wasmPath) {
615
615
  config.wasmPath = wasmPath;
616
616
  }
617
- console.log('[FaceDetectionEngine] Human.js config:', {
618
- backend: config.backend,
619
- modelBasePath: config.modelBasePath || '(using default)',
620
- wasmPath: config.wasmPath || '(using default)',
621
- userAgent: navigator.userAgent,
622
- 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
623
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
+ const criticalModels = ['face', 'antispoof', 'liveness'];
659
+ const missingModels = [];
660
+ for (const modelName of criticalModels) {
661
+ const model = human.models[modelName];
662
+ if (!model) {
663
+ missingModels.push(modelName);
664
+ console.error(`[FaceDetectionEngine] CRITICAL: Model '${modelName}' is missing!`);
665
+ }
666
+ else {
667
+ const isLoaded = model.loaded || model.state === 'loaded' || !!model.model;
668
+ // 检查模型是否有必要的内部结构(防止 "Cannot read properties of undefined (reading 'inputs')" 错误)
669
+ const hasExecutor = !!model['executor'];
670
+ const hasInputs = !!model.inputs && Array.isArray(model.inputs) && model.inputs.length > 0;
671
+ const hasModelUrl = !!model['modelUrl'];
672
+ console.log(`[FaceDetectionEngine] Model '${modelName}':`, {
673
+ loaded: isLoaded,
674
+ state: model.state,
675
+ hasModel: !!model.model,
676
+ hasExecutor,
677
+ hasInputs,
678
+ hasModelUrl,
679
+ inputsType: typeof model.inputs,
680
+ inputsLength: Array.isArray(model.inputs) ? model.inputs.length : 'N/A'
681
+ });
682
+ // 严格检查:模型必须有以下结构才能正常工作
683
+ if (!isLoaded || !hasExecutor || !hasModelUrl) {
684
+ missingModels.push(`${modelName} (incomplete)`);
685
+ console.error(`[FaceDetectionEngine] WARNING: Model '${modelName}' may not be fully loaded - missing structure`);
686
+ }
687
+ // 如果 inputs 未定义会导致 "Cannot read properties of undefined (reading 'inputs')" 错误
688
+ if (!hasInputs && modelName !== 'antispoof') {
689
+ console.warn(`[FaceDetectionEngine] WARNING: Model '${modelName}' has no inputs - may cause errors during detection`);
690
+ missingModels.push(`${modelName} (no inputs)`);
691
+ }
692
+ }
693
+ }
694
+ if (missingModels.length > 0) {
695
+ console.error('[FaceDetectionEngine] Some critical models failed to load:', missingModels);
696
+ throw new Error(`Critical models not loaded: ${missingModels.join(', ')}`);
697
+ }
698
+ // 打印加载的模型信息
699
+ if (human.models) {
700
+ const loadedModels = Object.entries(human.models).map(([name, model]) => ({
701
+ name,
702
+ loaded: model?.loaded || model?.state === 'loaded',
703
+ type: typeof model,
704
+ hasModel: !!model?.model
705
+ }));
706
+ console.log('[FaceDetectionEngine] All loaded models:', {
707
+ backend: human.config?.backend,
708
+ modelBasePath: human.config?.modelBasePath,
709
+ wasmPath: human.config?.wasmPath,
710
+ totalModels: Object.keys(human.models).length,
711
+ models: loadedModels,
712
+ allModelNames: Object.keys(human.models)
713
+ });
714
+ }
715
+ }
716
+ /**
717
+ * Try to load Human with a specific backend
718
+ * @param config The configuration object
719
+ * @param backend The backend to try
720
+ * @returns Human instance or null if fails
721
+ */
722
+ async function _tryLoadHumanWithBackend(backend, modelPath, wasmPath) {
723
+ const config = _createHumanConfig(backend, modelPath, wasmPath);
624
724
  const initStartTime = performance.now();
625
- console.log('[FaceDetectionEngine] Creating Human instance...');
626
725
  let human;
627
726
  try {
628
727
  human = new Human(config);
629
728
  }
630
729
  catch (error) {
631
730
  const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
632
- console.error('[FaceDetectionEngine] Failed to create Human instance:', errorMsg);
633
- throw new Error(`Human instantiation failed: ${errorMsg}`);
731
+ const stack = error instanceof Error ? error.stack : 'N/A';
732
+ console.error(`[FaceDetectionEngine] Failed to create Human instance (${backend}):`, {
733
+ errorMsg,
734
+ stack,
735
+ backend: config.backend,
736
+ userAgent: navigator.userAgent
737
+ });
738
+ return null;
634
739
  }
635
- const instanceCreateTime = performance.now() - initStartTime;
636
- console.log(`[FaceDetectionEngine] Human instance created, took ${instanceCreateTime.toFixed(2)}ms`);
637
740
  // 验证 Human 实例
638
741
  if (!human) {
639
- throw new Error('Human instance is null after creation');
742
+ console.error(`[FaceDetectionEngine] Human instance is null (${backend})`);
743
+ return null;
744
+ }
745
+ // 验证 Human 实例结构(早期检测 WASM 问题)
746
+ if (!human.config) {
747
+ console.warn('[FaceDetectionEngine] Warning: human.config is missing');
640
748
  }
641
- console.log('[FaceDetectionEngine] Loading Human.js models...');
642
- const modelLoadStartTime = performance.now();
643
749
  try {
644
- // 添加超时机制防止无限等待
645
- const loadTimeout = new Promise((_, reject) => {
646
- const timeoutId = setTimeout(() => {
647
- reject(new Error('Human.js load() timeout after 60 seconds - possible issue with model loading on mobile'));
648
- }, 60000);
649
- });
650
- // 竞速:哪个先完成就用哪个
651
- await Promise.race([
652
- human.load(),
653
- loadTimeout
654
- ]);
655
- const loadTime = performance.now() - modelLoadStartTime;
750
+ await _loadAndVerifyHuman(human);
656
751
  const totalTime = performance.now() - initStartTime;
657
- console.log('[FaceDetectionEngine] Human.js loaded successfully', {
658
- modelLoadTime: `${loadTime.toFixed(2)}ms`,
659
- totalInitTime: `${totalTime.toFixed(2)}ms`,
660
- version: human.version,
661
- config: human.config
662
- });
663
- // 验证加载后的 Human 实例有必要的方法和属性
664
- if (typeof human.detect !== 'function') {
665
- throw new Error('Human.detect method not available after loading');
666
- }
667
- if (!human.version) {
668
- console.warn('[FaceDetectionEngine] Human.js loaded but version is missing');
669
- }
670
- // 打印加载的模型信息
671
- if (human.models) {
672
- const loadedModels = Object.entries(human.models).map(([name, model]) => ({
673
- name,
674
- loaded: model?.loaded || model?.state === 'loaded',
675
- type: typeof model
676
- }));
677
- console.log('[FaceDetectionEngine] Loaded models:', {
678
- totalModels: Object.keys(human.models).length,
679
- models: loadedModels,
680
- allModels: Object.keys(human.models)
681
- });
682
- }
683
- else {
684
- console.warn('[FaceDetectionEngine] human.models is not available');
685
- }
686
- // 额外验证:检查模型是否加载成功
687
- if (!human.models || Object.keys(human.models).length === 0) {
688
- console.warn('[FaceDetectionEngine] Warning: human.models appears to be empty after loading');
689
- }
752
+ console.log(`[FaceDetectionEngine] Successfully loaded Human.js with ${backend} backend in ${totalTime.toFixed(2)}ms`);
690
753
  return human;
691
754
  }
692
755
  catch (error) {
693
756
  const errorMsg = error instanceof Error ? error.message : 'Unknown error';
694
- const stack = error instanceof Error ? error.stack : 'N/A';
695
- console.error('[FaceDetectionEngine] Human.js load failed:', {
696
- errorMsg,
697
- stack,
698
- userAgent: navigator.userAgent,
699
- platform: navigator.platform,
700
- humanVersion: human?.version,
701
- humanConfig: human?.config
702
- });
703
- throw new Error(`Human.js loading failed: ${errorMsg}`);
757
+ console.error(`[FaceDetectionEngine] Failed to load models with ${backend} backend:`, errorMsg);
758
+ return null;
704
759
  }
705
760
  }
761
+ /**
762
+ * Load Human.js
763
+ * @param modelPath - Path to model files (optional)
764
+ * @param wasmPath - Path to WASM files (optional)
765
+ * @param preferredBackend - Preferred TensorFlow backend: 'auto' | 'webgl' | 'wasm' (default: 'auto')
766
+ * @returns Promise that resolves with Human instance
767
+ */
768
+ async function loadHuman(modelPath, wasmPath, preferredBackend) {
769
+ const selectedBackend = _detectOptimalBackend(preferredBackend);
770
+ console.log('[FaceDetectionEngine] Starting Human.js initialization:', {
771
+ selectedBackend,
772
+ modelBasePath: modelPath || '(using default)',
773
+ wasmPath: wasmPath || '(using default)',
774
+ userAgent: navigator.userAgent,
775
+ platform: navigator.platform
776
+ });
777
+ // 尝试用主后端加载
778
+ const human = await _tryLoadHumanWithBackend(selectedBackend, modelPath, wasmPath);
779
+ if (human) {
780
+ return human;
781
+ }
782
+ console.log(`[FaceDetectionEngine] Human.js loading failed with ${selectedBackend} backend.`);
783
+ // 尝试用备选后端加载(最多一次降级)
784
+ let fallbackBackend;
785
+ if (selectedBackend === 'wasm' && _isWebGLAvailable()) {
786
+ fallbackBackend = 'webgl';
787
+ }
788
+ else if (selectedBackend === 'webgl') {
789
+ fallbackBackend = 'wasm';
790
+ }
791
+ if (fallbackBackend) {
792
+ console.warn(`[FaceDetectionEngine] Primary backend (${selectedBackend}) failed, attempting fallback to ${fallbackBackend}...`);
793
+ const humanFallback = await _tryLoadHumanWithBackend(fallbackBackend, modelPath, wasmPath);
794
+ if (humanFallback) {
795
+ return humanFallback;
796
+ }
797
+ throw new Error(`Human.js loading failed: both ${selectedBackend} and ${fallbackBackend} backends failed`);
798
+ }
799
+ throw new Error(`Human.js loading failed: ${selectedBackend} backend failed (no fallback available)`);
800
+ }
706
801
  /**
707
802
  * Extract OpenCV version from getBuildInformation
708
803
  * @returns version string like "4.12.0"
@@ -1741,28 +1836,59 @@
1741
1836
  }
1742
1837
  catch (humanError) {
1743
1838
  const errorMsg = humanError instanceof Error ? humanError.message : 'Unknown error';
1744
- console.error('[FaceDetectionEngine] Human.js loading failed with error:', errorMsg);
1745
- this.emitDebug('initialization', 'Human.js loading failed with exception', {
1839
+ const stack = humanError instanceof Error ? humanError.stack : 'N/A';
1840
+ // 分析错误类型,提供针对性的建议
1841
+ let errorContext = {
1746
1842
  error: errorMsg,
1747
- stack: humanError instanceof Error ? humanError.stack : 'N/A',
1843
+ stack,
1748
1844
  userAgent: navigator.userAgent,
1749
1845
  platform: navigator.platform,
1750
- browser: this.detectBrowserInfo()
1751
- }, 'error');
1846
+ browser: this.detectBrowserInfo(),
1847
+ backend: this.config.tensorflow_backend,
1848
+ source: 'human.js'
1849
+ };
1850
+ // 特定错误类型的诊断
1851
+ if (errorMsg.includes('inputs')) {
1852
+ errorContext.diagnosis = 'Human.js internal error: Model structure incomplete';
1853
+ errorContext.rootCause = 'Human.js library issue - models not fully loaded or WASM backend initialization incomplete';
1854
+ errorContext.suggestion = 'This is a Human.js library issue. Models may not have proper executor or inputs structure. Check WASM initialization and model integrity.';
1855
+ }
1856
+ else if (errorMsg.includes('timeout')) {
1857
+ errorContext.diagnosis = 'Model loading timeout';
1858
+ errorContext.suggestion = 'Network issue or model file too large - check network conditions';
1859
+ }
1860
+ else if (errorMsg.includes('Critical models not loaded')) {
1861
+ errorContext.diagnosis = 'Human.js failed to load required models';
1862
+ errorContext.rootCause = 'Models (face, antispoof, liveness) are missing or incomplete';
1863
+ errorContext.suggestion = 'Check model files and ensure WASM backend is properly initialized';
1864
+ }
1865
+ else if (errorMsg.includes('empty')) {
1866
+ errorContext.diagnosis = 'Models object is empty after loading';
1867
+ errorContext.suggestion = 'Model path may be incorrect or HTTP response failed';
1868
+ }
1869
+ else if (errorMsg.includes('incomplete')) {
1870
+ errorContext.diagnosis = 'Models loaded but structure is incomplete';
1871
+ errorContext.rootCause = 'Human.js internal issue - missing executor, inputs, or modelUrl';
1872
+ errorContext.suggestion = 'Ensure all model resources are fully loaded and accessible';
1873
+ }
1874
+ console.error('[FaceDetectionEngine] Human.js loading failed with detailed error:', errorContext);
1875
+ this.emitDebug('initialization', 'Human.js loading failed with exception', errorContext, 'error');
1752
1876
  this.emit('detector-loaded', {
1753
1877
  success: false,
1754
- error: `Failed to load Human.js: ${errorMsg}`
1878
+ error: `Failed to load Human.js: ${errorMsg}`,
1879
+ details: errorContext
1755
1880
  });
1756
1881
  this.emit('detector-error', {
1757
1882
  code: exports.ErrorCode.DETECTOR_NOT_INITIALIZED,
1758
- message: `Human.js loading error: ${errorMsg}`
1883
+ message: `Human.js loading error: ${errorMsg}`,
1884
+ details: errorContext
1759
1885
  });
1760
1886
  return;
1761
1887
  }
1762
1888
  const humanLoadTime = performance.now() - humanStartTime;
1763
1889
  if (!this.human) {
1764
1890
  const errorMsg = 'Failed to load Human.js: instance is null';
1765
- console.log('[FaceDetectionEngine] ' + errorMsg);
1891
+ console.error('[FaceDetectionEngine] ' + errorMsg);
1766
1892
  this.emitDebug('initialization', errorMsg, { loadTime: humanLoadTime }, 'error');
1767
1893
  this.emit('detector-loaded', {
1768
1894
  success: false,