slider-captcha-sdk 1.0.13 → 1.0.15

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.esm.js CHANGED
@@ -33,8 +33,36 @@ class PopupSliderCaptcha {
33
33
  return `:root{--sc-primary:#409eff;--sc-success:#67c23a;--sc-danger:#f56c6c;--sc-border:#e4e7eb;--sc-bg:linear-gradient(90deg, #f7f9fa 0%, #e8f4fd 100%);--sc-text:#333;--sc-text-light:#999;--sc-shadow:0 4px 20px rgba(0,0,0,.3);--sc-radius:8px;--sc-transition:.3s ease}.slider-captcha-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);z-index:9999;display:none;justify-content:center;align-items:center;opacity:0;transition:opacity var(--sc-transition)}.slider-captcha-overlay.show{opacity:1}.slider-captcha-modal{background:#fff;border-radius:var(--sc-radius);padding:20px;box-shadow:var(--sc-shadow);position:relative;max-width:90vw;max-height:90vh;transform:scale(.8) translateY(-20px);opacity:0;transition:all var(--sc-transition)}.slider-captcha-modal.show{transform:scale(1) translateY(0);opacity:1}.slider-captcha-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;padding-bottom:10px;border-bottom:1px solid var(--sc-border)}.slider-captcha-container{display:flex;align-items:center;position:relative;border-radius:4px;overflow:hidden;margin-bottom:15px;background:#837a7a;justify-content:center}.slider-captcha-track{width:100%;height:40px;line-height:40px;background:var(--sc-bg);border:1px solid var(--sc-border);border-radius:20px;position:relative;margin-bottom:15px;overflow:hidden}.slider-captcha-btn{width:38px;height:38px;background:#fff;border:1px solid #ccc;border-radius:50%;position:absolute;top:0;left:0;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 4px rgba(0,0,0,.1);transition:all var(--sc-transition);user-select:none;z-index:1}.slider-captcha-loading{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(255,255,255,.6);display:flex;align-items:center;justify-content:center;flex-direction:column;color:#666;font-size:14px;z-index:10;border-radius:4px}.slider-captcha-error{color:var(--sc-danger);font-size:12px;text-align:center;margin-top:10px;display:none}.slider-captcha-title{margin:0;font-size:16px;color:var(--sc-text)}.slider-captcha-close,.slider-captcha-refresh{background:none;border:none;cursor:pointer;color:var(--sc-text-light);padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:50%;transition:all var(--sc-transition);position:relative;font-size:0}.slider-captcha-close::before,.slider-captcha-close::after{content:'';position:absolute;width:16px;height:2px;background-color:var(--sc-text-light);border-radius:1px;transition:all var(--sc-transition)}.slider-captcha-close::before{transform:rotate(45deg)}.slider-captcha-close::after{transform:rotate(-45deg)}.slider-captcha-close:hover{background:#f5f5f5;transform:scale(1.1)}.slider-captcha-close:hover::before,.slider-captcha-close:hover::after{background-color:var(--sc-danger)}.slider-captcha-refresh{margin-left:10px}.slider-captcha-refresh svg{width:20px;height:20px;fill:var(--sc-text-light);transition:all var(--sc-transition)}.slider-captcha-refresh:hover{background:#f5f5f5;transform:scale(1.1)}.slider-captcha-refresh:hover svg{fill:var(--sc-primary);transform:rotate(180deg)}.slider-captcha-floating-time{position:absolute;bottom:-40px;left:50%;transform:translateX(-50%);color:#fff;font-size:12px;white-space:nowrap;opacity:0;pointer-events:none;z-index:10;transition:all var(--sc-transition);background:#fff;padding:2px 15px;border-radius:10px}.slider-captcha-floating-time.show{opacity:1;transform:translateX(-50%) translateY(-45px)}.slider-captcha-floating-time.success{color:var(--sc-success)}.slider-captcha-floating-time.fail{color:var(--sc-danger)}.slider-captcha-bg{width:100%;height:100%;object-fit:cover;display:block}.slider-captcha-piece{position:absolute;top:0;left:0;cursor:pointer;transition:none;z-index:2}.slider-captcha-finger{position:absolute;top:50%;left:10px;transform:translateY(-50%);font-size:20px;animation:fingerSlide 2s ease-in-out infinite;pointer-events:none;z-index:1;opacity:.6}.slider-captcha-hint{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--sc-text-light);font-size:14px;pointer-events:none;z-index:1;transition:all var(--sc-transition)}.slider-captcha-header-buttons{display:flex;align-items:center}@keyframes fingerSlide{0%{left:10px;opacity:.6}50%{opacity:1}100%{left:calc(50% - 10px);opacity:.6}}`
34
34
  }
35
35
 
36
+ // 优化:添加常量定义,提高可维护性
37
+ static CONSTANTS = {
38
+ CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
39
+ MAX_RETRY_ATTEMPTS: 3,
40
+ DEFAULT_TIMEOUT: 30000,
41
+ ANIMATION_DURATION: 300,
42
+ FLOATING_TIME_DURATION: 2500,
43
+ THROTTLE_DELAY: 16
44
+ }
45
+
46
+ // 优化:添加错误类型枚举
47
+ static ERROR_TYPES = {
48
+ NETWORK_ERROR: 'NETWORK_ERROR',
49
+ TIMEOUT_ERROR: 'TIMEOUT_ERROR',
50
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
51
+ IMAGE_LOAD_ERROR: 'IMAGE_LOAD_ERROR',
52
+ CAPTCHA_DATA_ERROR: 'CAPTCHA_DATA_ERROR'
53
+ }
54
+
36
55
  constructor(options = {}) {
37
- this.options = { ...PopupSliderCaptcha.DEFAULTS, ...options };
56
+ // 优化:合并默认配置,使用常量
57
+ this.options = {
58
+ ...PopupSliderCaptcha.DEFAULTS,
59
+ ...options,
60
+ timeout: options.timeout || PopupSliderCaptcha.CONSTANTS.DEFAULT_TIMEOUT,
61
+ maxRetries: options.maxRetries || PopupSliderCaptcha.CONSTANTS.MAX_RETRY_ATTEMPTS,
62
+ throttleDelay: options.throttleDelay || PopupSliderCaptcha.CONSTANTS.THROTTLE_DELAY
63
+ };
64
+
65
+ // 优化:初始化所有属性
38
66
  this.elements = {};
39
67
  this.state = this.createInitialState();
40
68
  this.captchaData = null;
@@ -45,11 +73,18 @@ class PopupSliderCaptcha {
45
73
  this.rafId = null;
46
74
  this.cachedDimensions = null;
47
75
  this.imageCache = new Map();
76
+ this.abortController = null;
48
77
 
49
78
  // 优化:使用箭头函数绑定this,避免重复bind调用
50
79
  this.throttledHandleMove = this.throttle((e) => this.handleMove(e), this.options.throttleDelay);
51
80
 
52
- this.init();
81
+ // 优化:添加错误处理
82
+ try {
83
+ this.init();
84
+ } catch (error) {
85
+ console.error('滑块验证码初始化失败:', error);
86
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR, error.message);
87
+ }
53
88
  }
54
89
 
55
90
  // 优化:提取初始状态创建为独立方法
@@ -376,7 +411,6 @@ class PopupSliderCaptcha {
376
411
  show() {
377
412
  this.state.isVisible = true;
378
413
  this.elements.overlay.style.display = "flex";
379
- this.elements.overlay.offsetHeight; // 强制重绘
380
414
 
381
415
  requestAnimationFrame(() => {
382
416
  this.elements.overlay.classList.add("show");
@@ -400,56 +434,158 @@ class PopupSliderCaptcha {
400
434
  }, 300);
401
435
  }
402
436
 
403
- // 优化:简化加载逻辑
437
+ // 优化:添加统一错误处理方法
438
+ handleError(errorType, message, originalError = null) {
439
+ const errorMessages = {
440
+ [PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
441
+ [PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR]: '请求超时,请重试',
442
+ [PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR]: '验证失败,请重试',
443
+ [PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR]: '图片加载失败,请刷新重试',
444
+ [PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR]: '验证码数据错误,请刷新重试'
445
+ };
446
+
447
+ const errorMessage = errorMessages[errorType] || message || '未知错误';
448
+
449
+ // 调用用户自定义错误处理
450
+ if (this.options.onError) {
451
+ this.options.onError({
452
+ type: errorType,
453
+ message: errorMessage,
454
+ originalError
455
+ });
456
+ }
457
+
458
+ this.showError(errorMessage);
459
+ console.error(`滑块验证码错误 [${errorType}]:`, errorMessage, originalError);
460
+ }
461
+
462
+ // 优化:简化加载逻辑,增强错误处理
404
463
  async loadCaptcha() {
405
464
  try {
406
465
  this.showLoading();
407
466
  this.startTime = Date.now();
408
467
 
409
- const controller = new AbortController();
410
- const timeoutId = this.safeSetTimeout(() => controller.abort(), this.options.timeout);
468
+ // 取消之前的请求
469
+ if (this.abortController) {
470
+ this.abortController.abort();
471
+ }
472
+ this.abortController = new AbortController();
411
473
 
412
474
  const response = await fetch(this.options.apiUrl, {
413
475
  method: "POST",
414
- headers: { "Content-Type": "application/json" },
415
- body: JSON.stringify({ place: 2, timestamp: Date.now() }),
416
- signal: controller.signal
476
+ headers: {
477
+ "Content-Type": "application/json",
478
+ ...this.options.headers
479
+ },
480
+ body: JSON.stringify({
481
+ place: 2,
482
+ timestamp: Date.now(),
483
+ ...this.options.requestData
484
+ }),
485
+ signal: this.abortController.signal
417
486
  });
418
487
 
419
- this.safeClearTimeout(timeoutId);
488
+ if (!response.ok) {
489
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
490
+ }
420
491
 
421
- if (!response.ok) throw new Error(`HTTP ${response.status}`)
422
492
  const data = await response.json();
423
493
 
424
- if (data.data?.canvasSrc && data.data?.blockSrc) {
425
- this.captchaData = data.data;
426
- this.showCaptcha();
427
- await this.renderCaptcha();
428
- } else {
494
+ // 优化:更严格的数据验证
495
+ if (!this.validateCaptchaData(data)) {
429
496
  throw new Error("验证码数据格式错误")
430
497
  }
498
+
499
+ this.captchaData = data.data;
500
+ this.showCaptcha();
501
+ await this.renderCaptcha();
431
502
  } catch (error) {
432
- const message = error.name === 'AbortError' ? "请求超时,请重试" : `加载验证码失败: ${error.message}`;
433
- this.showError(message);
503
+ if (error.name === 'AbortError') {
504
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '请求被取消');
505
+ } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
506
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
507
+ } else {
508
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR, error.message, error);
509
+ }
434
510
  }
435
511
  }
436
512
 
513
+ // 优化:添加验证码数据验证方法
514
+ validateCaptchaData(data) {
515
+ if (!data || typeof data !== 'object') return false
516
+
517
+ const requiredFields = ['canvasSrc', 'blockSrc', 'canvasWidth', 'canvasHeight', 'blockWidth', 'blockHeight', 'blockY', 'nonceStr'];
518
+ const dataObj = data.data || data;
519
+
520
+ return requiredFields.every(field => {
521
+ const value = dataObj[field];
522
+ return value !== null && value !== undefined && value !== ''
523
+ })
524
+ }
525
+
437
526
  async renderCaptcha() {
438
527
  return new Promise((resolve, reject) => {
439
- let loadedCount = 0;
440
- const onLoad = () => ++loadedCount === 2 && (this.hideLoading(), resolve());
441
- const onError = () => reject(new Error("图片加载失败"));
442
-
443
- this.loadImage(this.elements.backgroundImg, this.captchaData.canvasSrc, {
444
- width: this.captchaData.canvasWidth,
445
- height: this.captchaData.canvasHeight
446
- }, onLoad, onError);
447
-
448
- this.loadImage(this.elements.sliderImg, this.captchaData.blockSrc, {
449
- width: this.captchaData.blockWidth,
450
- height: this.captchaData.blockHeight,
451
- top: this.captchaData.blockY
452
- }, onLoad, onError);
528
+ let hasError = false;
529
+
530
+ // 优化:并行加载图片,提高性能
531
+ const loadPromises = [
532
+ this.loadImageAsync(this.elements.backgroundImg, this.captchaData.canvasSrc, {
533
+ width: this.captchaData.canvasWidth,
534
+ height: this.captchaData.canvasHeight
535
+ }),
536
+ this.loadImageAsync(this.elements.sliderImg, this.captchaData.blockSrc, {
537
+ width: this.captchaData.blockWidth,
538
+ height: this.captchaData.blockHeight,
539
+ top: this.captchaData.blockY
540
+ })
541
+ ];
542
+
543
+ Promise.all(loadPromises)
544
+ .then(() => {
545
+ if (!hasError) {
546
+ this.hideLoading();
547
+ resolve();
548
+ }
549
+ })
550
+ .catch((error) => {
551
+ if (!hasError) {
552
+ hasError = true;
553
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR, '图片加载失败', error);
554
+ reject(error);
555
+ }
556
+ });
557
+ })
558
+ }
559
+
560
+ // 优化:添加异步图片加载方法
561
+ async loadImageAsync(imgElement, src, styles) {
562
+ return new Promise((resolve, reject) => {
563
+ // 检查缓存
564
+ if (this.imageCache.has(src)) {
565
+ const cachedImg = this.imageCache.get(src);
566
+ imgElement.src = cachedImg.src;
567
+ this.applyStyles(imgElement, styles);
568
+ resolve();
569
+ return
570
+ }
571
+
572
+ const timeoutId = this.safeSetTimeout(() => {
573
+ reject(new Error('图片加载超时'));
574
+ }, 10000); // 10秒超时
575
+
576
+ imgElement.onload = () => {
577
+ this.safeClearTimeout(timeoutId);
578
+ this.imageCache.set(src, imgElement.cloneNode());
579
+ resolve();
580
+ };
581
+
582
+ imgElement.onerror = (error) => {
583
+ this.safeClearTimeout(timeoutId);
584
+ reject(error);
585
+ };
586
+
587
+ imgElement.src = src;
588
+ this.applyStyles(imgElement, styles);
453
589
  })
454
590
  }
455
591
 
@@ -536,38 +672,68 @@ class PopupSliderCaptcha {
536
672
  }
537
673
 
538
674
  try {
539
- const controller = new AbortController();
540
- const timeoutId = this.safeSetTimeout(() => controller.abort(), this.options.timeout);
675
+ // 取消之前的验证请求
676
+ if (this.abortController) {
677
+ this.abortController.abort();
678
+ }
679
+ this.abortController = new AbortController();
541
680
 
542
681
  const response = await fetch(this.options.verifyUrl, {
543
682
  method: "POST",
544
- headers: { "Content-Type": "application/json" },
683
+ headers: {
684
+ "Content-Type": "application/json",
685
+ ...this.options.headers
686
+ },
545
687
  body: JSON.stringify({
546
688
  loginVo: {
547
689
  nonceStr: this.captchaData.nonceStr,
548
690
  value: this.getPosition()
549
691
  },
550
- dragEventList: [...this.times]
692
+ dragEventList: [...this.times],
693
+ ...this.options.verifyData
551
694
  }),
552
- signal: controller.signal
695
+ signal: this.abortController.signal
553
696
  });
554
697
 
555
- this.safeClearTimeout(timeoutId);
698
+ if (!response.ok) {
699
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`)
700
+ }
556
701
 
557
- if (!response.ok) throw new Error(`HTTP ${response.status}`)
558
702
  const data = await response.json();
559
703
 
560
- if (data.code === "0" || data.success === true) {
561
- this.onVerifySuccess(data.data);
704
+ // 优化:更灵活的验证结果判断
705
+ if (this.isVerifySuccess(data)) {
706
+ this.onVerifySuccess(data.data || data.result);
562
707
  } else {
563
- this.onVerifyFail(data.message || "验证失败,请重试!");
708
+ this.onVerifyFail(data.message || data.msg || "验证失败,请重试!");
564
709
  }
565
710
  } catch (error) {
566
- const message = error.name === 'AbortError' ? "验证超时,请重试" : "网络错误";
567
- this.onVerifyFail(message);
711
+ if (error.name === 'AbortError') {
712
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '验证请求被取消');
713
+ } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
714
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
715
+ } else {
716
+ this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR, error.message, error);
717
+ }
568
718
  }
569
719
  }
570
720
 
721
+ // 优化:添加验证成功判断方法
722
+ isVerifySuccess(data) {
723
+ if (!data || typeof data !== 'object') return false
724
+
725
+ // 支持多种成功标识
726
+ const successIndicators = [
727
+ data.code === "0",
728
+ data.code === 0,
729
+ data.success === true,
730
+ data.status === 'success',
731
+ data.result === true
732
+ ];
733
+
734
+ return successIndicators.some(indicator => indicator === true)
735
+ }
736
+
571
737
  onVerifySuccess(ticket) {
572
738
  const duration = Date.now() - this.startTime;
573
739
  const durationText = `验证成功!耗时:${(duration / 1000).toFixed(2)}s`;
@@ -708,36 +874,66 @@ class PopupSliderCaptcha {
708
874
  }
709
875
 
710
876
  destroy() {
711
- // 恢复body样式
712
- document.body.style.userSelect = "";
713
- document.body.style.cursor = "";
877
+ try {
878
+ // 取消所有进行中的请求
879
+ if (this.abortController) {
880
+ this.abortController.abort();
881
+ this.abortController = null;
882
+ }
714
883
 
715
- // 清理所有定时器
716
- this.clearAllTimers();
884
+ // 恢复body样式
885
+ if (document.body) {
886
+ document.body.style.userSelect = "";
887
+ document.body.style.cursor = "";
888
+ }
717
889
 
718
- // 移除所有事件监听器
719
- this.removeAllEventListeners();
890
+ // 清理所有定时器
891
+ this.clearAllTimers();
720
892
 
721
- // 清理图片资源
722
- this.cleanupImages();
893
+ // 移除所有事件监听器
894
+ this.removeAllEventListeners();
723
895
 
724
- // 移除DOM元素
725
- if (this.elements.overlay?.parentNode) {
726
- this.elements.overlay.parentNode.removeChild(this.elements.overlay);
727
- }
896
+ // 清理图片资源
897
+ this.cleanupImages();
728
898
 
729
- // 清理样式表
730
- const styleElement = document.getElementById('slider-captcha-styles');
731
- if (styleElement) {
732
- styleElement.remove();
733
- }
899
+ // 移除DOM元素
900
+ if (this.elements?.overlay?.parentNode) {
901
+ this.elements.overlay.parentNode.removeChild(this.elements.overlay);
902
+ }
734
903
 
735
- // 清空所有属性
736
- Object.keys(this).forEach(key => {
737
- if (key !== 'constructor') {
738
- this[key] = null;
904
+ // 清理样式表(只有在没有其他实例使用时才删除)
905
+ const styleElement = document.getElementById('slider-captcha-styles');
906
+ if (styleElement && !this.hasOtherInstances()) {
907
+ styleElement.remove();
739
908
  }
740
- });
909
+
910
+ // 清空缓存
911
+ this.imageCache.clear();
912
+ this.cachedDimensions = null;
913
+
914
+ // 清空所有属性
915
+ Object.keys(this).forEach(key => {
916
+ if (key !== 'constructor') {
917
+ this[key] = null;
918
+ }
919
+ });
920
+
921
+ // 调用销毁回调
922
+ if (this.options.onDestroy) {
923
+ this.options.onDestroy();
924
+ }
925
+ } catch (error) {
926
+ console.error('销毁滑块验证码时出错:', error);
927
+ }
928
+ }
929
+
930
+ // 优化:检查是否有其他实例在使用样式
931
+ hasOtherInstances() {
932
+ // 简单的检查方式,可以通过全局计数器实现
933
+ if (typeof window !== 'undefined') {
934
+ return window.sliderCaptchaInstanceCount > 1
935
+ }
936
+ return false
741
937
  }
742
938
 
743
939
  static create(options) {
@@ -762,27 +958,48 @@ if (typeof module !== "undefined" && module.exports) {
762
958
  window.SliderCaptcha = PopupSliderCaptcha;
763
959
  }
764
960
 
961
+ // Add ES6 export for modern module systems
962
+ var PopupSliderCaptcha$1 = PopupSliderCaptcha;
963
+
765
964
  /**
766
965
  * 密码校验工具类
767
966
  * 提供密码加密和校验功能
768
967
  */
769
968
  class PasswordValidator {
969
+ // 优化:添加常量定义
970
+ static CONSTANTS = {
971
+ DEFAULT_TIMEOUT: 10000,
972
+ CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
973
+ MAX_RETRY_ATTEMPTS: 3,
974
+ MIN_PASSWORD_LENGTH: 1
975
+ }
976
+
977
+ // 优化:添加错误类型枚举
978
+ static ERROR_TYPES = {
979
+ NETWORK_ERROR: 'NETWORK_ERROR',
980
+ TIMEOUT_ERROR: 'TIMEOUT_ERROR',
981
+ ENCRYPTION_ERROR: 'ENCRYPTION_ERROR',
982
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
983
+ PUBLIC_KEY_ERROR: 'PUBLIC_KEY_ERROR'
984
+ }
985
+
770
986
  constructor(options = {}) {
987
+ // 优化:合并配置,使用常量
771
988
  this.options = {
772
- // 获取公钥的接口地址
773
989
  publicKeyUrl: options.publicKeyUrl || '/microservice/strongPassword/getPublicKey',
774
- // 密码校验接口地址
775
990
  validateUrl: options.validateUrl || '/microservice/strongPassword/checkPassword',
776
- // 请求超时时间
777
- timeout: options.timeout || 10000,
778
- // 自定义请求头
991
+ timeout: options.timeout || PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,
779
992
  headers: options.headers || {},
993
+ retryAttempts: options.retryAttempts || PasswordValidator.CONSTANTS.MAX_RETRY_ATTEMPTS,
994
+ cacheDuration: options.cacheDuration || PasswordValidator.CONSTANTS.CACHE_DURATION,
780
995
  ...options
781
996
  };
782
997
 
783
998
  // 缓存公钥,避免重复请求
784
999
  this.publicKeyCache = null;
785
1000
  this.publicKeyExpiry = null;
1001
+ this.retryCount = 0;
1002
+ this.abortController = null;
786
1003
  }
787
1004
 
788
1005
  /**
@@ -790,31 +1007,94 @@ class PasswordValidator {
790
1007
  * @returns {Promise<string>} 公钥字符串
791
1008
  */
792
1009
  async getPublicKey() {
793
- // 检查缓存是否有效(5分钟有效期)
1010
+ // 检查缓存是否有效
794
1011
  if (this.publicKeyCache && this.publicKeyExpiry && Date.now() < this.publicKeyExpiry) {
795
1012
  return this.publicKeyCache
796
1013
  }
797
1014
 
798
1015
  try {
1016
+ // 取消之前的请求
1017
+ if (this.abortController) {
1018
+ this.abortController.abort();
1019
+ }
1020
+ this.abortController = new AbortController();
1021
+
799
1022
  const response = await this.makeRequest(this.options.publicKeyUrl, {
800
- method: 'GET'
1023
+ method: 'GET',
1024
+ signal: this.abortController.signal
801
1025
  });
802
1026
 
803
- if (response.code !== '0' ) {
804
- throw new Error('获取公钥失败:' + (response.message || '未知错误'))
1027
+ // 优化:更灵活的成功判断
1028
+ if (!this.isSuccessResponse(response)) {
1029
+ throw new Error('获取公钥失败:' + (response.message || response.msg || '未知错误'))
1030
+ }
1031
+
1032
+ // 验证公钥格式
1033
+ const publicKey = response.data || response.result;
1034
+ if (!this.validatePublicKey(publicKey)) {
1035
+ throw new Error('公钥格式无效')
805
1036
  }
806
1037
 
807
- // 缓存公钥,设置5分钟过期时间
808
- this.publicKeyCache = response.data;
809
- this.publicKeyExpiry = Date.now() + 5 * 60 * 1000;
1038
+ // 缓存公钥
1039
+ this.publicKeyCache = publicKey;
1040
+ this.publicKeyExpiry = Date.now() + this.options.cacheDuration;
810
1041
 
811
1042
  return this.publicKeyCache
812
1043
  } catch (error) {
813
1044
  console.error('获取公钥失败:', error);
1045
+ this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR, error.message, error);
814
1046
  throw new Error('获取公钥失败: ' + error.message)
815
1047
  }
816
1048
  }
817
1049
 
1050
+ // 优化:添加响应成功判断方法
1051
+ isSuccessResponse(response) {
1052
+ if (!response || typeof response !== 'object') return false
1053
+
1054
+ const successIndicators = [
1055
+ response.code === '0',
1056
+ response.code === 0,
1057
+ response.success === true,
1058
+ response.status === 'success',
1059
+ response.result === true
1060
+ ];
1061
+
1062
+ return successIndicators.some(indicator => indicator === true)
1063
+ }
1064
+
1065
+ // 优化:添加公钥验证方法
1066
+ validatePublicKey(publicKey) {
1067
+ if (!publicKey || typeof publicKey !== 'string') return false
1068
+
1069
+ // 基本的RSA公钥格式检查
1070
+ const rsaPattern = /^-----BEGIN PUBLIC KEY-----[\s\S]*-----END PUBLIC KEY-----$/;
1071
+ return rsaPattern.test(publicKey.trim())
1072
+ }
1073
+
1074
+ // 优化:添加统一错误处理方法
1075
+ handleError(errorType, message, originalError = null) {
1076
+ const errorMessages = {
1077
+ [PasswordValidator.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
1078
+ [PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]: '请求超时,请重试',
1079
+ [PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]: '密码加密失败',
1080
+ [PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]: '密码验证失败',
1081
+ [PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]: '获取公钥失败'
1082
+ };
1083
+
1084
+ const errorMessage = errorMessages[errorType] || message || '未知错误';
1085
+
1086
+ // 调用用户自定义错误处理
1087
+ if (this.options.onError) {
1088
+ this.options.onError({
1089
+ type: errorType,
1090
+ message: errorMessage,
1091
+ originalError
1092
+ });
1093
+ }
1094
+
1095
+ console.error(`密码校验器错误 [${errorType}]:`, errorMessage, originalError);
1096
+ }
1097
+
818
1098
  /**
819
1099
  * 使用RSA公钥加密密码
820
1100
  * @param {string} password 原始密码
@@ -823,9 +1103,22 @@ class PasswordValidator {
823
1103
  */
824
1104
  encryptPassword(password, publicKey) {
825
1105
  try {
1106
+ // 优化:输入验证
1107
+ if (!password || typeof password !== 'string') {
1108
+ throw new Error('密码不能为空')
1109
+ }
1110
+
1111
+ if (password.length < PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH) {
1112
+ throw new Error('密码长度不足')
1113
+ }
1114
+
1115
+ if (!publicKey || typeof publicKey !== 'string') {
1116
+ throw new Error('公钥不能为空')
1117
+ }
1118
+
826
1119
  // 使用导入的JSEncrypt进行RSA加密
827
- if (!JSEncrypt) {
828
- throw new Error('JSEncrypt库未正确加载')
1120
+ if (typeof JSEncrypt === 'undefined') {
1121
+ throw new Error('JSEncrypt库未正确加载,请确保已引入JSEncrypt库')
829
1122
  }
830
1123
 
831
1124
  const encrypt = new JSEncrypt();
@@ -833,12 +1126,13 @@ class PasswordValidator {
833
1126
  const encrypted = encrypt.encrypt(password);
834
1127
 
835
1128
  if (!encrypted) {
836
- throw new Error('密码加密失败')
1129
+ throw new Error('密码加密失败,可能是公钥格式不正确')
837
1130
  }
838
1131
 
839
1132
  return encrypted
840
1133
  } catch (error) {
841
1134
  console.error('密码加密失败:', error);
1135
+ this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR, error.message, error);
842
1136
  throw new Error('密码加密失败: ' + error.message)
843
1137
  }
844
1138
  }
@@ -846,12 +1140,24 @@ class PasswordValidator {
846
1140
  /**
847
1141
  * 校验密码
848
1142
  * @param {string} password 原始密码
849
- * @param {string} userName 用户
1143
+ * @param {string} userName 用户名
850
1144
  * @param {Object} additionalData 额外的校验数据
851
1145
  * @returns {Promise<Object>} 校验结果
852
1146
  */
853
1147
  async validatePassword(password, userName, additionalData = {}) {
854
1148
  try {
1149
+ // 优化:输入验证
1150
+ if (!password || typeof password !== 'string') {
1151
+ throw new Error('密码不能为空')
1152
+ }
1153
+
1154
+ if (!userName || typeof userName !== 'string') {
1155
+ throw new Error('用户名不能为空')
1156
+ }
1157
+
1158
+ // 重置重试计数
1159
+ this.retryCount = 0;
1160
+
855
1161
  // 1. 获取公钥
856
1162
  const publicKey = await this.getPublicKey();
857
1163
 
@@ -869,15 +1175,34 @@ class PasswordValidator {
869
1175
  })
870
1176
  });
871
1177
 
1178
+ // 优化:统一返回格式
872
1179
  return {
873
- ...response
1180
+ success: this.isSuccessResponse(response),
1181
+ data: response.data || response.result,
1182
+ message: response.message || response.msg || '验证完成',
1183
+ code: response.code || response.status,
1184
+ originalResponse: response
874
1185
  }
875
1186
  } catch (error) {
876
1187
  console.error('密码校验失败:', error);
1188
+
1189
+ // 优化:根据错误类型返回不同的错误信息
1190
+ let errorType = PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;
1191
+ if (error.message.includes('网络') || error.message.includes('fetch')) {
1192
+ errorType = PasswordValidator.ERROR_TYPES.NETWORK_ERROR;
1193
+ } else if (error.message.includes('超时') || error.message.includes('timeout')) {
1194
+ errorType = PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR;
1195
+ } else if (error.message.includes('加密')) {
1196
+ errorType = PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR;
1197
+ }
1198
+
1199
+ this.handleError(errorType, error.message, error);
1200
+
877
1201
  return {
878
1202
  success: false,
879
- message: error.msg,
880
- code: 'VALIDATION_ERROR'
1203
+ message: error.message || '密码校验失败',
1204
+ code: errorType,
1205
+ error: error
881
1206
  }
882
1207
  }
883
1208
  }
@@ -889,8 +1214,15 @@ class PasswordValidator {
889
1214
  * @returns {Promise<Object>} 响应结果
890
1215
  */
891
1216
  async makeRequest(url, options = {}) {
892
- const controller = new AbortController();
893
- const timeoutId = setTimeout(() => controller.abort(), this.options.timeout);
1217
+ // 优化:使用实例的abortController
1218
+ if (this.abortController) {
1219
+ this.abortController.abort();
1220
+ }
1221
+ this.abortController = new AbortController();
1222
+
1223
+ const timeoutId = setTimeout(() => {
1224
+ this.abortController.abort();
1225
+ }, this.options.timeout);
894
1226
 
895
1227
  try {
896
1228
  const response = await fetch(url, {
@@ -900,7 +1232,7 @@ class PasswordValidator {
900
1232
  ...this.options.headers,
901
1233
  ...options.headers
902
1234
  },
903
- signal: controller.signal
1235
+ signal: this.abortController.signal
904
1236
  });
905
1237
 
906
1238
  clearTimeout(timeoutId);
@@ -909,14 +1241,27 @@ class PasswordValidator {
909
1241
  throw new Error(`HTTP错误: ${response.status} ${response.statusText}`)
910
1242
  }
911
1243
 
912
- return await response.json()
1244
+ const data = await response.json();
1245
+
1246
+ // 优化:记录请求日志(开发环境)
1247
+ if (this.options.debug) {
1248
+ console.log('密码校验器请求:', { url, options, response: data });
1249
+ }
1250
+
1251
+ return data
913
1252
  } catch (error) {
914
1253
  clearTimeout(timeoutId);
915
1254
 
916
1255
  if (error.name === 'AbortError') {
1256
+ this.handleError(PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR, '请求超时', error);
917
1257
  throw new Error('请求超时')
918
1258
  }
919
1259
 
1260
+ if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
1261
+ this.handleError(PasswordValidator.ERROR_TYPES.NETWORK_ERROR, '网络连接失败', error);
1262
+ throw new Error('网络连接失败')
1263
+ }
1264
+
920
1265
  throw error
921
1266
  }
922
1267
  }
@@ -938,8 +1283,65 @@ class PasswordValidator {
938
1283
  // 清除缓存,使用新配置重新获取
939
1284
  this.clearCache();
940
1285
  }
1286
+
1287
+ /**
1288
+ * 取消当前请求
1289
+ */
1290
+ cancelRequest() {
1291
+ if (this.abortController) {
1292
+ this.abortController.abort();
1293
+ this.abortController = null;
1294
+ }
1295
+ }
1296
+
1297
+ /**
1298
+ * 检查是否正在请求中
1299
+ * @returns {boolean} 是否正在请求
1300
+ */
1301
+ isRequesting() {
1302
+ return this.abortController && !this.abortController.signal.aborted
1303
+ }
1304
+
1305
+ /**
1306
+ * 获取缓存状态
1307
+ * @returns {Object} 缓存状态信息
1308
+ */
1309
+ getCacheStatus() {
1310
+ return {
1311
+ hasCache: !!this.publicKeyCache,
1312
+ isExpired: this.publicKeyExpiry ? Date.now() > this.publicKeyExpiry : true,
1313
+ expiryTime: this.publicKeyExpiry,
1314
+ remainingTime: this.publicKeyExpiry ? Math.max(0, this.publicKeyExpiry - Date.now()) : 0
1315
+ }
1316
+ }
1317
+
1318
+ /**
1319
+ * 销毁实例,清理资源
1320
+ */
1321
+ destroy() {
1322
+ try {
1323
+ // 取消所有进行中的请求
1324
+ this.cancelRequest();
1325
+
1326
+ // 清除缓存
1327
+ this.clearCache();
1328
+
1329
+ // 重置状态
1330
+ this.retryCount = 0;
1331
+
1332
+ // 调用销毁回调
1333
+ if (this.options.onDestroy) {
1334
+ this.options.onDestroy();
1335
+ }
1336
+ } catch (error) {
1337
+ console.error('销毁密码校验器时出错:', error);
1338
+ }
1339
+ }
941
1340
  }
942
1341
 
1342
+ // 导出类和创建实例的工厂函数
1343
+ var PasswordValidator$1 = PasswordValidator;
1344
+
943
1345
  /**
944
1346
  * 创建密码校验器实例的工厂函数
945
1347
  * @param {Object} options 配置选项
@@ -967,19 +1369,19 @@ async function validatePassword(password, options = {}, additionalData = {}) {
967
1369
 
968
1370
  // 默认导出(向后兼容)
969
1371
  var index = {
970
- PopupSliderCaptcha,
971
- PasswordValidator,
1372
+ PopupSliderCaptcha: PopupSliderCaptcha$1,
1373
+ PasswordValidator: PasswordValidator$1,
972
1374
  createPasswordValidator,
973
1375
  validatePassword
974
1376
  };
975
1377
 
976
1378
  // 全局注册(用于UMD构建)
977
1379
  if (typeof window !== 'undefined') {
978
- window.SliderCaptcha = PopupSliderCaptcha;
979
- window.PopupSliderCaptcha = PopupSliderCaptcha;
980
- window.PasswordValidator = PasswordValidator;
1380
+ window.SliderCaptcha = PopupSliderCaptcha$1;
1381
+ window.PopupSliderCaptcha = PopupSliderCaptcha$1;
1382
+ window.PasswordValidator = PasswordValidator$1;
981
1383
  window.createPasswordValidator = createPasswordValidator;
982
1384
  window.validatePassword = validatePassword;
983
1385
  }
984
1386
 
985
- export { PasswordValidator, PopupSliderCaptcha, createPasswordValidator, index as default, validatePassword };
1387
+ export { PasswordValidator$1 as PasswordValidator, PopupSliderCaptcha$1 as PopupSliderCaptcha, createPasswordValidator, index as default, validatePassword };