slider-captcha-sdk 1.0.12 → 1.0.14
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.d.ts +26 -55
- package/dist/index.esm.js +502 -100
- package/dist/index.min.js +1 -1
- package/dist/password-validator.d.ts +83 -4
- package/dist/password-validator.esm.js +224 -24
- package/dist/password-validator.min.js +1 -1
- package/dist/slider-captcha.d.ts +78 -8
- package/dist/slider-captcha.esm.js +265 -69
- package/dist/slider-captcha.min.js +1 -1
- package/package.json +1 -1
package/dist/index.esm.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import JSEncrypt from 'jsencrypt';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* 纯JavaScript弹窗滑块验证码组件
|
|
5
3
|
*/
|
|
@@ -33,8 +31,36 @@ class PopupSliderCaptcha {
|
|
|
33
31
|
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
32
|
}
|
|
35
33
|
|
|
34
|
+
// 优化:添加常量定义,提高可维护性
|
|
35
|
+
static CONSTANTS = {
|
|
36
|
+
CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
|
|
37
|
+
MAX_RETRY_ATTEMPTS: 3,
|
|
38
|
+
DEFAULT_TIMEOUT: 30000,
|
|
39
|
+
ANIMATION_DURATION: 300,
|
|
40
|
+
FLOATING_TIME_DURATION: 2500,
|
|
41
|
+
THROTTLE_DELAY: 16
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 优化:添加错误类型枚举
|
|
45
|
+
static ERROR_TYPES = {
|
|
46
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
47
|
+
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
|
|
48
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
49
|
+
IMAGE_LOAD_ERROR: 'IMAGE_LOAD_ERROR',
|
|
50
|
+
CAPTCHA_DATA_ERROR: 'CAPTCHA_DATA_ERROR'
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
constructor(options = {}) {
|
|
37
|
-
|
|
54
|
+
// 优化:合并默认配置,使用常量
|
|
55
|
+
this.options = {
|
|
56
|
+
...PopupSliderCaptcha.DEFAULTS,
|
|
57
|
+
...options,
|
|
58
|
+
timeout: options.timeout || PopupSliderCaptcha.CONSTANTS.DEFAULT_TIMEOUT,
|
|
59
|
+
maxRetries: options.maxRetries || PopupSliderCaptcha.CONSTANTS.MAX_RETRY_ATTEMPTS,
|
|
60
|
+
throttleDelay: options.throttleDelay || PopupSliderCaptcha.CONSTANTS.THROTTLE_DELAY
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
// 优化:初始化所有属性
|
|
38
64
|
this.elements = {};
|
|
39
65
|
this.state = this.createInitialState();
|
|
40
66
|
this.captchaData = null;
|
|
@@ -45,11 +71,18 @@ class PopupSliderCaptcha {
|
|
|
45
71
|
this.rafId = null;
|
|
46
72
|
this.cachedDimensions = null;
|
|
47
73
|
this.imageCache = new Map();
|
|
74
|
+
this.abortController = null;
|
|
48
75
|
|
|
49
76
|
// 优化:使用箭头函数绑定this,避免重复bind调用
|
|
50
77
|
this.throttledHandleMove = this.throttle((e) => this.handleMove(e), this.options.throttleDelay);
|
|
51
78
|
|
|
52
|
-
|
|
79
|
+
// 优化:添加错误处理
|
|
80
|
+
try {
|
|
81
|
+
this.init();
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error('滑块验证码初始化失败:', error);
|
|
84
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR, error.message);
|
|
85
|
+
}
|
|
53
86
|
}
|
|
54
87
|
|
|
55
88
|
// 优化:提取初始状态创建为独立方法
|
|
@@ -376,7 +409,6 @@ class PopupSliderCaptcha {
|
|
|
376
409
|
show() {
|
|
377
410
|
this.state.isVisible = true;
|
|
378
411
|
this.elements.overlay.style.display = "flex";
|
|
379
|
-
this.elements.overlay.offsetHeight; // 强制重绘
|
|
380
412
|
|
|
381
413
|
requestAnimationFrame(() => {
|
|
382
414
|
this.elements.overlay.classList.add("show");
|
|
@@ -400,56 +432,158 @@ class PopupSliderCaptcha {
|
|
|
400
432
|
}, 300);
|
|
401
433
|
}
|
|
402
434
|
|
|
403
|
-
//
|
|
435
|
+
// 优化:添加统一错误处理方法
|
|
436
|
+
handleError(errorType, message, originalError = null) {
|
|
437
|
+
const errorMessages = {
|
|
438
|
+
[PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
|
|
439
|
+
[PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR]: '请求超时,请重试',
|
|
440
|
+
[PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR]: '验证失败,请重试',
|
|
441
|
+
[PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR]: '图片加载失败,请刷新重试',
|
|
442
|
+
[PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR]: '验证码数据错误,请刷新重试'
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const errorMessage = errorMessages[errorType] || message || '未知错误';
|
|
446
|
+
|
|
447
|
+
// 调用用户自定义错误处理
|
|
448
|
+
if (this.options.onError) {
|
|
449
|
+
this.options.onError({
|
|
450
|
+
type: errorType,
|
|
451
|
+
message: errorMessage,
|
|
452
|
+
originalError
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
this.showError(errorMessage);
|
|
457
|
+
console.error(`滑块验证码错误 [${errorType}]:`, errorMessage, originalError);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// 优化:简化加载逻辑,增强错误处理
|
|
404
461
|
async loadCaptcha() {
|
|
405
462
|
try {
|
|
406
463
|
this.showLoading();
|
|
407
464
|
this.startTime = Date.now();
|
|
408
465
|
|
|
409
|
-
|
|
410
|
-
|
|
466
|
+
// 取消之前的请求
|
|
467
|
+
if (this.abortController) {
|
|
468
|
+
this.abortController.abort();
|
|
469
|
+
}
|
|
470
|
+
this.abortController = new AbortController();
|
|
411
471
|
|
|
412
472
|
const response = await fetch(this.options.apiUrl, {
|
|
413
473
|
method: "POST",
|
|
414
|
-
headers: {
|
|
415
|
-
|
|
416
|
-
|
|
474
|
+
headers: {
|
|
475
|
+
"Content-Type": "application/json",
|
|
476
|
+
...this.options.headers
|
|
477
|
+
},
|
|
478
|
+
body: JSON.stringify({
|
|
479
|
+
place: 2,
|
|
480
|
+
timestamp: Date.now(),
|
|
481
|
+
...this.options.requestData
|
|
482
|
+
}),
|
|
483
|
+
signal: this.abortController.signal
|
|
417
484
|
});
|
|
418
485
|
|
|
419
|
-
|
|
486
|
+
if (!response.ok) {
|
|
487
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
488
|
+
}
|
|
420
489
|
|
|
421
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
422
490
|
const data = await response.json();
|
|
423
491
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.showCaptcha();
|
|
427
|
-
await this.renderCaptcha();
|
|
428
|
-
} else {
|
|
492
|
+
// 优化:更严格的数据验证
|
|
493
|
+
if (!this.validateCaptchaData(data)) {
|
|
429
494
|
throw new Error("验证码数据格式错误")
|
|
430
495
|
}
|
|
496
|
+
|
|
497
|
+
this.captchaData = data.data;
|
|
498
|
+
this.showCaptcha();
|
|
499
|
+
await this.renderCaptcha();
|
|
431
500
|
} catch (error) {
|
|
432
|
-
|
|
433
|
-
|
|
501
|
+
if (error.name === 'AbortError') {
|
|
502
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '请求被取消');
|
|
503
|
+
} else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
|
|
504
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
|
|
505
|
+
} else {
|
|
506
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR, error.message, error);
|
|
507
|
+
}
|
|
434
508
|
}
|
|
435
509
|
}
|
|
436
510
|
|
|
511
|
+
// 优化:添加验证码数据验证方法
|
|
512
|
+
validateCaptchaData(data) {
|
|
513
|
+
if (!data || typeof data !== 'object') return false
|
|
514
|
+
|
|
515
|
+
const requiredFields = ['canvasSrc', 'blockSrc', 'canvasWidth', 'canvasHeight', 'blockWidth', 'blockHeight', 'blockY', 'nonceStr'];
|
|
516
|
+
const dataObj = data.data || data;
|
|
517
|
+
|
|
518
|
+
return requiredFields.every(field => {
|
|
519
|
+
const value = dataObj[field];
|
|
520
|
+
return value !== null && value !== undefined && value !== ''
|
|
521
|
+
})
|
|
522
|
+
}
|
|
523
|
+
|
|
437
524
|
async renderCaptcha() {
|
|
438
525
|
return new Promise((resolve, reject) => {
|
|
439
|
-
let
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
526
|
+
let hasError = false;
|
|
527
|
+
|
|
528
|
+
// 优化:并行加载图片,提高性能
|
|
529
|
+
const loadPromises = [
|
|
530
|
+
this.loadImageAsync(this.elements.backgroundImg, this.captchaData.canvasSrc, {
|
|
531
|
+
width: this.captchaData.canvasWidth,
|
|
532
|
+
height: this.captchaData.canvasHeight
|
|
533
|
+
}),
|
|
534
|
+
this.loadImageAsync(this.elements.sliderImg, this.captchaData.blockSrc, {
|
|
535
|
+
width: this.captchaData.blockWidth,
|
|
536
|
+
height: this.captchaData.blockHeight,
|
|
537
|
+
top: this.captchaData.blockY
|
|
538
|
+
})
|
|
539
|
+
];
|
|
540
|
+
|
|
541
|
+
Promise.all(loadPromises)
|
|
542
|
+
.then(() => {
|
|
543
|
+
if (!hasError) {
|
|
544
|
+
this.hideLoading();
|
|
545
|
+
resolve();
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
.catch((error) => {
|
|
549
|
+
if (!hasError) {
|
|
550
|
+
hasError = true;
|
|
551
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR, '图片加载失败', error);
|
|
552
|
+
reject(error);
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// 优化:添加异步图片加载方法
|
|
559
|
+
async loadImageAsync(imgElement, src, styles) {
|
|
560
|
+
return new Promise((resolve, reject) => {
|
|
561
|
+
// 检查缓存
|
|
562
|
+
if (this.imageCache.has(src)) {
|
|
563
|
+
const cachedImg = this.imageCache.get(src);
|
|
564
|
+
imgElement.src = cachedImg.src;
|
|
565
|
+
this.applyStyles(imgElement, styles);
|
|
566
|
+
resolve();
|
|
567
|
+
return
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const timeoutId = this.safeSetTimeout(() => {
|
|
571
|
+
reject(new Error('图片加载超时'));
|
|
572
|
+
}, 10000); // 10秒超时
|
|
573
|
+
|
|
574
|
+
imgElement.onload = () => {
|
|
575
|
+
this.safeClearTimeout(timeoutId);
|
|
576
|
+
this.imageCache.set(src, imgElement.cloneNode());
|
|
577
|
+
resolve();
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
imgElement.onerror = (error) => {
|
|
581
|
+
this.safeClearTimeout(timeoutId);
|
|
582
|
+
reject(error);
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
imgElement.src = src;
|
|
586
|
+
this.applyStyles(imgElement, styles);
|
|
453
587
|
})
|
|
454
588
|
}
|
|
455
589
|
|
|
@@ -536,39 +670,69 @@ class PopupSliderCaptcha {
|
|
|
536
670
|
}
|
|
537
671
|
|
|
538
672
|
try {
|
|
539
|
-
|
|
540
|
-
|
|
673
|
+
// 取消之前的验证请求
|
|
674
|
+
if (this.abortController) {
|
|
675
|
+
this.abortController.abort();
|
|
676
|
+
}
|
|
677
|
+
this.abortController = new AbortController();
|
|
541
678
|
|
|
542
679
|
const response = await fetch(this.options.verifyUrl, {
|
|
543
680
|
method: "POST",
|
|
544
|
-
headers: {
|
|
681
|
+
headers: {
|
|
682
|
+
"Content-Type": "application/json",
|
|
683
|
+
...this.options.headers
|
|
684
|
+
},
|
|
545
685
|
body: JSON.stringify({
|
|
546
686
|
loginVo: {
|
|
547
687
|
nonceStr: this.captchaData.nonceStr,
|
|
548
688
|
value: this.getPosition()
|
|
549
689
|
},
|
|
550
|
-
dragEventList: [...this.times]
|
|
690
|
+
dragEventList: [...this.times],
|
|
691
|
+
...this.options.verifyData
|
|
551
692
|
}),
|
|
552
|
-
signal:
|
|
693
|
+
signal: this.abortController.signal
|
|
553
694
|
});
|
|
554
695
|
|
|
555
|
-
|
|
696
|
+
if (!response.ok) {
|
|
697
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
|
|
698
|
+
}
|
|
556
699
|
|
|
557
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}`)
|
|
558
700
|
const data = await response.json();
|
|
559
701
|
|
|
560
|
-
|
|
561
|
-
|
|
702
|
+
// 优化:更灵活的验证结果判断
|
|
703
|
+
if (this.isVerifySuccess(data)) {
|
|
704
|
+
this.onVerifySuccess(data.data || data.result);
|
|
562
705
|
} else {
|
|
563
|
-
this.onVerifyFail(data.message || "验证失败,请重试!");
|
|
706
|
+
this.onVerifyFail(data.message || data.msg || "验证失败,请重试!");
|
|
564
707
|
}
|
|
565
708
|
} catch (error) {
|
|
566
|
-
|
|
567
|
-
|
|
709
|
+
if (error.name === 'AbortError') {
|
|
710
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR, '验证请求被取消');
|
|
711
|
+
} else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
|
|
712
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR, '网络连接失败');
|
|
713
|
+
} else {
|
|
714
|
+
this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR, error.message, error);
|
|
715
|
+
}
|
|
568
716
|
}
|
|
569
717
|
}
|
|
570
718
|
|
|
571
|
-
|
|
719
|
+
// 优化:添加验证成功判断方法
|
|
720
|
+
isVerifySuccess(data) {
|
|
721
|
+
if (!data || typeof data !== 'object') return false
|
|
722
|
+
|
|
723
|
+
// 支持多种成功标识
|
|
724
|
+
const successIndicators = [
|
|
725
|
+
data.code === "0",
|
|
726
|
+
data.code === 0,
|
|
727
|
+
data.success === true,
|
|
728
|
+
data.status === 'success',
|
|
729
|
+
data.result === true
|
|
730
|
+
];
|
|
731
|
+
|
|
732
|
+
return successIndicators.some(indicator => indicator === true)
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
onVerifySuccess(ticket) {
|
|
572
736
|
const duration = Date.now() - this.startTime;
|
|
573
737
|
const durationText = `验证成功!耗时:${(duration / 1000).toFixed(2)}s`;
|
|
574
738
|
|
|
@@ -577,7 +741,7 @@ class PopupSliderCaptcha {
|
|
|
577
741
|
|
|
578
742
|
this.safeSetTimeout(() => {
|
|
579
743
|
this.options.onSuccess?.({
|
|
580
|
-
|
|
744
|
+
ticket: ticket,
|
|
581
745
|
timestamp: Date.now(),
|
|
582
746
|
duration
|
|
583
747
|
});
|
|
@@ -708,36 +872,66 @@ class PopupSliderCaptcha {
|
|
|
708
872
|
}
|
|
709
873
|
|
|
710
874
|
destroy() {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
875
|
+
try {
|
|
876
|
+
// 取消所有进行中的请求
|
|
877
|
+
if (this.abortController) {
|
|
878
|
+
this.abortController.abort();
|
|
879
|
+
this.abortController = null;
|
|
880
|
+
}
|
|
714
881
|
|
|
715
|
-
|
|
716
|
-
|
|
882
|
+
// 恢复body样式
|
|
883
|
+
if (document.body) {
|
|
884
|
+
document.body.style.userSelect = "";
|
|
885
|
+
document.body.style.cursor = "";
|
|
886
|
+
}
|
|
717
887
|
|
|
718
|
-
|
|
719
|
-
|
|
888
|
+
// 清理所有定时器
|
|
889
|
+
this.clearAllTimers();
|
|
720
890
|
|
|
721
|
-
|
|
722
|
-
|
|
891
|
+
// 移除所有事件监听器
|
|
892
|
+
this.removeAllEventListeners();
|
|
723
893
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
this.elements.overlay.parentNode.removeChild(this.elements.overlay);
|
|
727
|
-
}
|
|
894
|
+
// 清理图片资源
|
|
895
|
+
this.cleanupImages();
|
|
728
896
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
}
|
|
897
|
+
// 移除DOM元素
|
|
898
|
+
if (this.elements?.overlay?.parentNode) {
|
|
899
|
+
this.elements.overlay.parentNode.removeChild(this.elements.overlay);
|
|
900
|
+
}
|
|
734
901
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (
|
|
738
|
-
|
|
902
|
+
// 清理样式表(只有在没有其他实例使用时才删除)
|
|
903
|
+
const styleElement = document.getElementById('slider-captcha-styles');
|
|
904
|
+
if (styleElement && !this.hasOtherInstances()) {
|
|
905
|
+
styleElement.remove();
|
|
739
906
|
}
|
|
740
|
-
|
|
907
|
+
|
|
908
|
+
// 清空缓存
|
|
909
|
+
this.imageCache.clear();
|
|
910
|
+
this.cachedDimensions = null;
|
|
911
|
+
|
|
912
|
+
// 清空所有属性
|
|
913
|
+
Object.keys(this).forEach(key => {
|
|
914
|
+
if (key !== 'constructor') {
|
|
915
|
+
this[key] = null;
|
|
916
|
+
}
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
// 调用销毁回调
|
|
920
|
+
if (this.options.onDestroy) {
|
|
921
|
+
this.options.onDestroy();
|
|
922
|
+
}
|
|
923
|
+
} catch (error) {
|
|
924
|
+
console.error('销毁滑块验证码时出错:', error);
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// 优化:检查是否有其他实例在使用样式
|
|
929
|
+
hasOtherInstances() {
|
|
930
|
+
// 简单的检查方式,可以通过全局计数器实现
|
|
931
|
+
if (typeof window !== 'undefined') {
|
|
932
|
+
return window.sliderCaptchaInstanceCount > 1
|
|
933
|
+
}
|
|
934
|
+
return false
|
|
741
935
|
}
|
|
742
936
|
|
|
743
937
|
static create(options) {
|
|
@@ -762,27 +956,50 @@ if (typeof module !== "undefined" && module.exports) {
|
|
|
762
956
|
window.SliderCaptcha = PopupSliderCaptcha;
|
|
763
957
|
}
|
|
764
958
|
|
|
959
|
+
// Add ES6 export for modern module systems
|
|
960
|
+
var PopupSliderCaptcha$1 = PopupSliderCaptcha;
|
|
961
|
+
|
|
962
|
+
// import JSEncrypt from 'jsencrypt'
|
|
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
|
-
//
|
|
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
|
-
|
|
804
|
-
|
|
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
|
-
//
|
|
808
|
-
this.publicKeyCache =
|
|
809
|
-
this.publicKeyExpiry = Date.now() +
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
880
|
-
code:
|
|
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
|
-
|
|
893
|
-
|
|
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:
|
|
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
|
-
|
|
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 };
|