@sssxyd/face-liveness-detector 0.2.32 → 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
@@ -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,120 @@
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
+ 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);
629
724
  const initStartTime = performance.now();
630
- console.log('[FaceDetectionEngine] Creating Human instance...');
631
725
  let human;
632
726
  try {
633
727
  human = new Human(config);
@@ -635,164 +729,74 @@
635
729
  catch (error) {
636
730
  const errorMsg = error instanceof Error ? error.message : 'Unknown error during Human instantiation';
637
731
  const stack = error instanceof Error ? error.stack : 'N/A';
638
- console.error('[FaceDetectionEngine] Failed to create Human instance:', {
732
+ console.error(`[FaceDetectionEngine] Failed to create Human instance (${backend}):`, {
639
733
  errorMsg,
640
734
  stack,
641
735
  backend: config.backend,
642
736
  userAgent: navigator.userAgent
643
737
  });
644
- throw new Error(`Human instantiation failed: ${errorMsg}`);
738
+ return null;
645
739
  }
646
- const instanceCreateTime = performance.now() - initStartTime;
647
- console.log(`[FaceDetectionEngine] Human instance created, took ${instanceCreateTime.toFixed(2)}ms`);
648
740
  // 验证 Human 实例
649
741
  if (!human) {
650
- throw new Error('Human instance is null after creation');
742
+ console.error(`[FaceDetectionEngine] Human instance is null (${backend})`);
743
+ return null;
651
744
  }
652
745
  // 验证 Human 实例结构(早期检测 WASM 问题)
653
746
  if (!human.config) {
654
747
  console.warn('[FaceDetectionEngine] Warning: human.config is missing');
655
748
  }
656
- console.log('[FaceDetectionEngine] Loading Human.js models...');
657
- const modelLoadStartTime = performance.now();
658
749
  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;
750
+ await _loadAndVerifyHuman(human);
690
751
  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
- }
752
+ console.log(`[FaceDetectionEngine] Successfully loaded Human.js with ${backend} backend in ${totalTime.toFixed(2)}ms`);
764
753
  return human;
765
754
  }
766
755
  catch (error) {
767
756
  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
- }
757
+ console.error(`[FaceDetectionEngine] Failed to load models with ${backend} backend:`, errorMsg);
758
+ return null;
759
+ }
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;
793
796
  }
794
- throw new Error(`Human.js loading failed: ${errorMsg}`);
797
+ throw new Error(`Human.js loading failed: both ${selectedBackend} and ${fallbackBackend} backends failed`);
795
798
  }
799
+ throw new Error(`Human.js loading failed: ${selectedBackend} backend failed (no fallback available)`);
796
800
  }
797
801
  /**
798
802
  * Extract OpenCV version from getBuildInformation