slider-captcha-sdk 1.0.17 → 1.0.19
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 +182 -211
- package/dist/index.min.js +1 -1
- package/dist/password-validator.esm.js +34 -59
- package/dist/password-validator.min.js +1 -1
- package/dist/slider-captcha.esm.js +148 -152
- package/package.json +1 -1
package/dist/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jsencrypt")):"function"==typeof define&&define.amd?define(["exports","jsencrypt"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptchaSDK={},t.JSEncrypt)}(this,function(t,JSEncrypt){"use strict";function e(t){return new PasswordValidator(t)}async function s(t,e={},s={}){const i=new PasswordValidator(e),a=s.userName||"";return await i.validatePassword(t,a,s)}var i,a,r;class PopupSliderCaptcha{static DEFAULTS={width:320,height:155,sliderSize:38,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify",throttleDelay:16,clickMaskClose:!1};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static getStyles(){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}}"}static CONSTANTS={CACHE_DURATION:3e5,MAX_RETRY_ATTEMPTS:3,DEFAULT_TIMEOUT:3e4,FLOATING_TIME_DURATION:2500,THROTTLE_DELAY:16};static ERROR_TYPES={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",IMAGE_LOAD_ERROR:"IMAGE_LOAD_ERROR",CAPTCHA_DATA_ERROR:"CAPTCHA_DATA_ERROR"};constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t,timeout:t.timeout||PopupSliderCaptcha.CONSTANTS.DEFAULT_TIMEOUT,maxRetries:t.maxRetries||PopupSliderCaptcha.CONSTANTS.MAX_RETRY_ATTEMPTS,throttleDelay:t.throttleDelay||PopupSliderCaptcha.CONSTANTS.THROTTLE_DELAY},this.elements={},this.state=this.createInitialState(),this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.timers=new Set,this.rafId=null,this.cachedDimensions=null,this.imageCache=new Map,this.abortController=null,this.throttledHandleMove=this.throttle(t=>this.handleMove(t),this.options.throttleDelay);try{this.init()}catch(t){this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR,t.message)}}createInitialState(){return{isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0,isLoading:!1}}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=PopupSliderCaptcha.getStyles(),document.head.appendChild(t)}createElements(){const{elements:t,options:e}=this;[["overlay","div",PopupSliderCaptcha.CSS_CLASSES.overlay],["modal","div",PopupSliderCaptcha.CSS_CLASSES.modal],["header","div",PopupSliderCaptcha.CSS_CLASSES.header],["title","h3","slider-captcha-title","安全验证"],["closeBtn","button","slider-captcha-close"],["refreshBtn","button","slider-captcha-refresh"],["container","div",PopupSliderCaptcha.CSS_CLASSES.container],["backgroundImg","img","slider-captcha-bg"],["sliderImg","img","slider-captcha-piece"],["loadingText","div",PopupSliderCaptcha.CSS_CLASSES.loading,"加载中..."],["floatingTime","div","slider-captcha-floating-time"],["track","div",PopupSliderCaptcha.CSS_CLASSES.track],["fingerAnimation","div","slider-captcha-finger","👉"],["btn","div",PopupSliderCaptcha.CSS_CLASSES.btn],["icon","div","","→"],["hint","div",PopupSliderCaptcha.CSS_CLASSES.hint,"向右滑动完成验证"],["error","div",PopupSliderCaptcha.CSS_CLASSES.error]].forEach(([e,s,i,a])=>{t[e]=this.createElement(s,i,a)}),t.container.style.cssText=`width:${e.width}px;height:${e.height}px`,t.refreshBtn.innerHTML='<svg viewBox="0 0 24 24"><path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"/></svg>',this.assembleDOM(),this.setInitialState()}createElement(t,e="",s=""){const i=document.createElement(t);return e&&(i.className=e),s&&(i.textContent=s),i}assembleDOM(){const{elements:t}=this,e=this.createElement("div","slider-captcha-header-buttons");e.append(t.refreshBtn,t.closeBtn),t.header.append(t.title,e),t.container.append(t.backgroundImg,t.sliderImg,t.loadingText,t.floatingTime),t.btn.appendChild(t.icon),t.track.append(t.fingerAnimation,t.btn,t.hint),t.modal.append(t.header,t.container,t.track,t.error),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){Object.assign(this.elements.container.style,{display:"none"}),Object.assign(this.elements.track.style,{display:"none"})}bindEvents(){const{elements:t}=this;this.addEventListener(t.closeBtn,"click",()=>this.hide()),this.addEventListener(t.refreshBtn,"click",()=>this.refresh()),this.addEventListener(t.overlay,"click",e=>{e.target===t.overlay&&this.options.clickMaskClose&&this.hide()}),this.addEventListener(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.addEventListener(document,"visibilitychange",()=>this.handleVisibilityChange()),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:t=>this.handleStart(t),move:this.throttledHandleMove,end:()=>this.handleEnd()};this.addEventListener(t.btn,"mousedown",e.start),this.addEventListener(t.btn,"touchstart",e.start),this.addEventListener(t.sliderImg,"mousedown",e.start),this.addEventListener(t.sliderImg,"touchstart",e.start),this.addEventListener(document,"mousemove",e.move,{passive:!1}),this.addEventListener(document,"touchmove",e.move,{passive:!1}),this.addEventListener(document,"mouseup",e.end),this.addEventListener(document,"touchend",e.end)}addEventListener(t,e,s,i={}){t&&"function"==typeof s&&(t.addEventListener(e,s,i),this.eventListeners.push({element:t,event:e,handler:s,options:i}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:s,options:i})=>{try{t?.removeEventListener?.(e,s,i)}catch(t){}}),this.eventListeners.length=0}getDimensions(){if(!this.cachedDimensions){const t=this.elements.track.offsetWidth,e=this.elements.btn.offsetWidth;this.cachedDimensions={trackWidth:t,btnWidth:e,maxX:t-e}}return this.cachedDimensions}getPosition(){const{maxX:t}=this.getDimensions(),e=this.state.currentX/t;return Math.round(e*(this.options.width-this.options.sliderSize))}handleStart(t){!this.captchaData||this.state.isDragging||this.state.isLoading||(t.preventDefault(),this.state.isDragging=!0,this.state.startX=this.getClientX(t)-this.state.currentX,this.dragStartTime=Date.now(),this.times=[{time:Date.now(),position:this.getPosition()}],this.setTransition(!1),this.updateUIState("dragging"),this.cachedDimensions=null)}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,{maxX:s}=this.getDimensions();this.state.currentX=Math.max(0,Math.min(e,s)),this.times.push({time:Date.now(),position:this.getPosition()}),this.rafId&&cancelAnimationFrame(this.rafId),this.rafId=requestAnimationFrame(()=>this.updateSliderPosition())}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.verify())}handleVisibilityChange(){const t=document.hidden?"paused":"running";this.elements.fingerAnimation&&(this.elements.fingerAnimation.style.animationPlayState=t)}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";requestAnimationFrame(()=>{this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e})}updateUIState(t){const{elements:e}=this,s={dragging:()=>{e.hint.style.opacity="0",e.fingerAnimation.style.display="none"},success:()=>{Object.assign(e.btn.style,{background:"var(--sc-success)"}),Object.assign(e.icon.style,{innerHTML:"✓",color:"white"}),e.icon.innerHTML="✓"},fail:()=>{Object.assign(e.btn.style,{background:"var(--sc-danger)"}),Object.assign(e.icon.style,{innerHTML:"✗",color:"white"}),e.icon.innerHTML="✗"},reset:()=>{Object.assign(e.btn.style,{background:"white"}),Object.assign(e.icon.style,{color:"#666"}),e.icon.innerHTML="→",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","var(--sc-text-light)")},loading:()=>{e.hint.style.opacity="0",e.fingerAnimation.style.display="none",Object.assign(e.track.style,{pointerEvents:"none",opacity:"0.6"})}};s[t]&&requestAnimationFrame(()=>{s[t](),"loading"!==t&&Object.assign(e.track.style,{pointerEvents:"auto",opacity:"1"})})}updateHintText(t,e){requestAnimationFrame(()=>{Object.assign(this.elements.hint,{textContent:t}),Object.assign(this.elements.hint.style,{color:e,opacity:"1"})})}updateSliderPosition(){const{elements:t,options:e,state:s}=this,{maxX:i}=this.getDimensions(),a=s.currentX/i*(e.width-e.sliderSize),r=s.currentX/i;requestAnimationFrame(()=>{t.btn.style.transform=`translateX(${s.currentX}px)`,t.sliderImg.style.transform=`translateX(${a}px)`,t.fingerAnimation.style.opacity=.8>r?"0.6":"0"})}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),this.safeSetTimeout(()=>{this.elements.overlay.style.display="none",document.body.removeChild(this.elements.overlay),this.reset(),this.options.onClose?.(),this.elements.overlay=null},300)}handleError(t,e,s=null){const i={[PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR]:"网络连接失败,请检查网络设置",[PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR]:"请求超时,请重试",[PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR]:"验证失败,请重试",[PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR]:"图片加载失败,请刷新重试",[PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR]:"验证码数据错误,请刷新重试"}[t]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:i,originalError:s}),this.showError(i)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now(),this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const t=await fetch(this.options.apiUrl,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({timestamp:Date.now(),...this.options.requestData}),signal:this.abortController.signal});if(!t.ok)throw Error(`HTTP ${t.status}: ${t.statusText}`);const e=await t.json();if(!this.validateCaptchaData(e))throw Error("验证码数据格式错误");this.captchaData=e.data,this.showCaptcha(),await this.renderCaptcha()}catch(t){"AbortError"===t.name?this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR,"请求被取消"):t.message.includes("Failed to fetch")||t.message.includes("NetworkError")?this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR,"网络连接失败"):this.handleError(PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR,t.message,t)}}validateCaptchaData(t){if(!t||"object"!=typeof t)return!1;const e=t.data||t;return["canvasSrc","blockSrc","canvasWidth","canvasHeight","blockWidth","blockHeight","blockY","nonceStr"].every(t=>{const s=e[t];return null!=s&&""!==s})}async renderCaptcha(){return new Promise((t,e)=>{let s=!1;const i=[this.loadImageAsync(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight}),this.loadImageAsync(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY})];Promise.all(i).then(()=>{s||(this.hideLoading(),t())}).catch(t=>{s||(s=!0,this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR,"图片加载失败",t),e(t))})})}async loadImageAsync(t,e,s){return new Promise((i,a)=>{if(this.imageCache.has(e)){const a=this.imageCache.get(e);return t.src=a.src,this.applyStyles(t,s),void i()}const r=this.safeSetTimeout(()=>{a(Error("图片加载超时"))},1e4);t.onload=()=>{this.safeClearTimeout(r),this.imageCache.set(e,t.cloneNode()),i()},t.onerror=t=>{this.safeClearTimeout(r),a(t)},t.src=e,this.applyStyles(t,s)})}applyStyles(t,e){Object.entries(e).forEach(([e,s])=>{t.style[e]="number"==typeof s?s+"px":s})}showLoading(){this.state.isLoading=!0,this.batchUpdateStyles({container:{display:"block"},loadingText:{display:"flex"},error:{display:"none"}}),this.updateUIState("loading")}hideLoading(){this.state.isLoading=!1,this.batchUpdateStyles({loadingText:{display:"none"}}),this.updateUIState("reset")}showCaptcha(){this.batchUpdateStyles({container:{display:"block"},track:{display:"block"},error:{display:"none"}})}showError(t){this.hideLoading(),this.batchUpdateStyles({error:{display:"block",textContent:t}})}batchUpdateStyles(t){requestAnimationFrame(()=>{Object.entries(t).forEach(([t,e])=>{const s=this.elements[t];s&&Object.entries(e).forEach(([t,e])=>{"textContent"===t?s.textContent=e:s.style[t]=e})})})}async verify(){if(this.captchaData)try{this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const t=await fetch(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times],...this.options.verifyData}),signal:this.abortController.signal});if(!t.ok)throw Error(`HTTP ${t.status}: ${t.statusText}`);const e=await t.json();this.isVerifySuccess(e)?this.onVerifySuccess(e.data||e.result):this.onVerifyFail(e.message||e.msg||"验证失败,请重试!")}catch(t){"AbortError"===t.name?this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR,"验证请求被取消"):t.message.includes("Failed to fetch")||t.message.includes("NetworkError")?this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR,"网络连接失败"):this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR,t.message,t)}else this.onVerifyFail("验证码数据丢失")}isVerifySuccess(t){return!(!t||"object"!=typeof t)&&["0"===t.code,0===t.code,!0===t.success,"success"===t.status,!0===t.result].some(t=>!0===t)}onVerifySuccess(t){const e=this.dragStartTime?Date.now()-this.dragStartTime:Date.now()-this.startTime,s=`验证成功!耗时:${(e/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showFloatingTime(s,"success"),this.safeSetTimeout(()=>{this.options.onSuccess?.({ticket:t,timestamp:Date.now(),duration:e}),this.hide()},2e3)}showFloatingTime(t,e="success"){const{elements:s}=this;s.floatingTime.textContent=t,s.floatingTime.className="slider-captcha-floating-time "+e,this.safeSetTimeout(()=>s.floatingTime.classList.add("show"),100),this.safeSetTimeout(()=>{s.floatingTime.className="slider-captcha-floating-time"},2500)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.showFloatingTime(t,"fail"),this.safeSetTimeout(()=>{this.state.retryCount<this.options.maxRetries?this.reset():this.refresh()},2500)}reset(){this.clearAllTimers(),Object.assign(this.state,{isDragging:!1,currentX:0,startX:0,isLoading:!1}),this.times=[],this.startTime=null,this.dragStartTime=null,this.cachedDimensions=null,requestAnimationFrame(()=>{this.setTransition(!0),this.elements.btn.style.transform="translateX(0px)",this.elements.sliderImg.style.transform="translateX(0px)",this.updateUIState("reset"),this.elements.error.style.display="none"})}refresh(){this.reset(),this.state.retryCount=0,this.loadCaptcha()}safeSetTimeout(t,e){const s=setTimeout(()=>{this.timers.delete(s),t()},e);return this.timers.add(s),s}safeClearTimeout(t){t&&(clearTimeout(t),this.timers.delete(t))}clearAllTimers(){this.timers.forEach(t=>{clearTimeout(t),clearInterval(t)}),this.timers.clear(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null)}cleanupImages(){this.elements.backgroundImg&&(this.elements.backgroundImg.src="",this.elements.backgroundImg.onload=null,this.elements.backgroundImg.onerror=null),this.elements.sliderImg&&(this.elements.sliderImg.src="",this.elements.sliderImg.onload=null,this.elements.sliderImg.onerror=null),this.imageCache.clear()}throttle(t,e){let s=0;return function(...i){const a=Date.now();if(a-s>=e)return s=a,t.apply(this,i)}}destroy(){try{this.abortController&&(this.abortController.abort(),this.abortController=null),document.body&&(document.body.style.userSelect="",document.body.style.cursor=""),this.clearAllTimers(),this.removeAllEventListeners(),this.cleanupImages(),this.elements?.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay);const t=document.getElementById("slider-captcha-styles");t&&t.remove(),this.imageCache.clear(),this.cachedDimensions=null,Object.keys(this).forEach(t=>{"constructor"!==t&&(this[t]=null)}),this.options.onDestroy&&this.options.onDestroy()}catch(t){}}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),i=PopupSliderCaptcha;class PasswordValidator{static CONSTANTS={DEFAULT_TIMEOUT:1e4,CACHE_DURATION:3e5,MIN_PASSWORD_LENGTH:1};static ERROR_TYPES={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",ENCRYPTION_ERROR:"ENCRYPTION_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",PUBLIC_KEY_ERROR:"PUBLIC_KEY_ERROR"};constructor(t={}){this.options={publicKeyUrl:t.publicKeyUrl||"/microservice/strongPassword/getPublicKey",validateUrl:t.validateUrl||"/microservice/strongPassword/checkPassword",timeout:t.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:t.headers||{},cacheDuration:t.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...t},this.publicKeyCache=null,this.publicKeyExpiry=null,this.abortController=null}async getPublicKey(){if(this.publicKeyCache&&this.publicKeyExpiry&&Date.now()<this.publicKeyExpiry)return this.publicKeyCache;try{this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const t=await this.makeRequest(this.options.publicKeyUrl,{method:"GET",signal:this.abortController.signal});if(!this.isSuccessResponse(t))throw Error("获取公钥失败:"+(t.message||t.msg||"未知错误"));const e=t.data||t.result;if(!this.validatePublicKey(e))throw Error("公钥格式无效");return this.publicKeyCache=e,this.publicKeyExpiry=Date.now()+this.options.cacheDuration,this.publicKeyCache}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR,t.message,t),Error("获取公钥失败: "+t.message)}}isSuccessResponse(t){return!(!t||"object"!=typeof t)&&["0"===t.code,0===t.code,!0===t.success,"success"===t.status,!0===t.result].some(t=>!0===t)}validatePublicKey(t){return!(!t||"string"!=typeof t)}handleError(t,e,s=null){const i={[PasswordValidator.ERROR_TYPES.NETWORK_ERROR]:"网络连接失败,请检查网络设置",[PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]:"请求超时,请重试",[PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]:"密码加密失败",[PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]:"密码验证失败",[PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]:"获取公钥失败"}[t]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:i,originalError:s})}encryptPassword(t,e){try{if(!t||"string"!=typeof t)throw Error("密码不能为空");if(t.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!e||"string"!=typeof e)throw Error("公钥不能为空");if(void 0===JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const s=new JSEncrypt;s.setPublicKey(e);const i=s.encrypt(t);if(!i)throw Error("密码加密失败,可能是公钥格式不正确");return i}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,e,s={}){try{if(!t||"string"!=typeof t)throw Error("密码不能为空");if(!e||"string"!=typeof e)throw Error("用户名不能为空");const i=await this.getPublicKey(),a=this.encryptPassword(t,i),r=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:e,password:a,timestamp:Date.now(),...s})});return{success:this.isSuccessResponse(r),data:r.data||r.result,message:r.message||r.msg||"验证完成",code:r.code||r.status,originalResponse:r}}catch(t){let e=PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;return t.message.includes("网络")||t.message.includes("fetch")?e=PasswordValidator.ERROR_TYPES.NETWORK_ERROR:t.message.includes("超时")||t.message.includes("timeout")?e=PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR:t.message.includes("加密")&&(e=PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR),this.handleError(e,t.message,t),{success:!1,message:t.message||"密码校验失败",code:e,error:t}}}async makeRequest(t,e={}){this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const s=setTimeout(()=>{this.abortController.abort()},this.options.timeout);try{const i=await fetch(t,{...e,headers:{"Content-Type":"application/json",...this.options.headers,...e.headers},signal:this.abortController.signal});if(clearTimeout(s),!i.ok)throw Error(`HTTP错误: ${i.status} ${i.statusText}`);const a=await i.json();return this.options.debug,a}catch(t){if(clearTimeout(s),"AbortError"===t.name)throw this.handleError(PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR,"请求超时",t),Error("请求超时");if(t.message.includes("Failed to fetch")||t.message.includes("NetworkError"))throw this.handleError(PasswordValidator.ERROR_TYPES.NETWORK_ERROR,"网络连接失败",t),Error("网络连接失败");throw t}}clearCache(){this.publicKeyCache=null,this.publicKeyExpiry=null}updateOptions(t){this.options={...this.options,...t},this.clearCache()}cancelRequest(){this.abortController&&(this.abortController.abort(),this.abortController=null)}isRequesting(){return this.abortController&&!this.abortController.signal.aborted}getCacheStatus(){return{hasCache:!!this.publicKeyCache,isExpired:!this.publicKeyExpiry||Date.now()>this.publicKeyExpiry,expiryTime:this.publicKeyExpiry,remainingTime:this.publicKeyExpiry?Math.max(0,this.publicKeyExpiry-Date.now()):0}}destroy(){try{this.cancelRequest(),this.clearCache(),this.options.onDestroy&&this.options.onDestroy()}catch(t){}}}r={PopupSliderCaptcha:i,PasswordValidator:a=PasswordValidator,createPasswordValidator:e,validatePassword:s},"undefined"!=typeof window&&(window.SliderCaptcha=i,window.PopupSliderCaptcha=i,window.PasswordValidator=a,window.createPasswordValidator=e,window.validatePassword=s),t.PasswordValidator=a,t.PopupSliderCaptcha=i,t.createPasswordValidator=e,t.default=r,t.validatePassword=s,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jsencrypt")):"function"==typeof define&&define.amd?define(["exports","jsencrypt"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptchaSDK={},t.JSEncrypt)}(this,function(t,JSEncrypt){"use strict";function e(t){return new PasswordValidator(t)}async function s(t,e={},s={}){const i=new PasswordValidator(e),a=s.userName||"";return await i.validatePassword(t,a,s)}var i,a,r;class PopupSliderCaptcha{static DEFAULTS={width:320,height:155,sliderSize:38,maxRetries:3,timeout:3e4,apiUrl:"/api/captcha",verifyUrl:"/api/captcha/verify",throttleDelay:16,clickMaskClose:!1};static CSS_CLASSES={overlay:"slider-captcha-overlay",modal:"slider-captcha-modal",header:"slider-captcha-header",container:"slider-captcha-container",track:"slider-captcha-track",btn:"slider-captcha-btn",hint:"slider-captcha-hint",loading:"slider-captcha-loading",error:"slider-captcha-error"};static getStyles(){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}}"}static CONSTANTS={CACHE_DURATION:3e5,MAX_RETRY_ATTEMPTS:3,DEFAULT_TIMEOUT:3e4,FLOATING_TIME_DURATION:2500,THROTTLE_DELAY:16};static ERROR_TYPES={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",IMAGE_LOAD_ERROR:"IMAGE_LOAD_ERROR",CAPTCHA_DATA_ERROR:"CAPTCHA_DATA_ERROR"};constructor(t={}){this.options={...PopupSliderCaptcha.DEFAULTS,...t,timeout:t.timeout||PopupSliderCaptcha.CONSTANTS.DEFAULT_TIMEOUT,maxRetries:t.maxRetries||PopupSliderCaptcha.CONSTANTS.MAX_RETRY_ATTEMPTS,throttleDelay:t.throttleDelay||PopupSliderCaptcha.CONSTANTS.THROTTLE_DELAY},this.elements={},this.state=this.createInitialState(),this.captchaData=null,this.times=[],this.startTime=null,this.eventListeners=[],this.timers=new Set,this.rafId=null,this.cachedDimensions=null,this.imageCache=new Map,this.abortController=null,this.throttledHandleMove=this.throttle(t=>this.handleMove(t),this.options.throttleDelay);try{this.init()}catch(t){this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR,t.message)}}createInitialState(){return{isVisible:!1,isDragging:!1,currentX:0,startX:0,retryCount:0,isLoading:!1}}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=document.createElement("style");t.id="slider-captcha-styles",t.textContent=PopupSliderCaptcha.getStyles(),document.head.appendChild(t)}createElements(){const{elements:t,options:e}=this;[["overlay","div",PopupSliderCaptcha.CSS_CLASSES.overlay],["modal","div",PopupSliderCaptcha.CSS_CLASSES.modal],["header","div",PopupSliderCaptcha.CSS_CLASSES.header],["title","h3","slider-captcha-title","安全验证"],["closeBtn","button","slider-captcha-close"],["refreshBtn","button","slider-captcha-refresh"],["container","div",PopupSliderCaptcha.CSS_CLASSES.container],["backgroundImg","img","slider-captcha-bg"],["sliderImg","img","slider-captcha-piece"],["loadingText","div",PopupSliderCaptcha.CSS_CLASSES.loading,"加载中..."],["floatingTime","div","slider-captcha-floating-time"],["track","div",PopupSliderCaptcha.CSS_CLASSES.track],["fingerAnimation","div","slider-captcha-finger","👉"],["btn","div",PopupSliderCaptcha.CSS_CLASSES.btn],["icon","div","","→"],["hint","div",PopupSliderCaptcha.CSS_CLASSES.hint,"向右滑动完成验证"],["error","div",PopupSliderCaptcha.CSS_CLASSES.error]].forEach(([e,s,i,a])=>{t[e]=this.createElement(s,i,a)}),t.container.style.cssText=`width:${e.width}px;height:${e.height}px`,t.refreshBtn.innerHTML='<svg viewBox="0 0 24 24"><path d="M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z"/></svg>',this.assembleDOM(),this.setInitialState()}createElement(t,e="",s=""){const i=document.createElement(t);return e&&(i.className=e),s&&(i.textContent=s),i}assembleDOM(){const{elements:t}=this,e=this.createElement("div","slider-captcha-header-buttons");e.append(t.refreshBtn,t.closeBtn),t.header.append(t.title,e),t.container.append(t.backgroundImg,t.sliderImg,t.loadingText,t.floatingTime),t.btn.appendChild(t.icon),t.track.append(t.fingerAnimation,t.btn,t.hint),t.modal.append(t.header,t.container,t.track,t.error),t.overlay.appendChild(t.modal),document.body.appendChild(t.overlay)}setInitialState(){Object.assign(this.elements.container.style,{display:"none"}),Object.assign(this.elements.track.style,{display:"none"})}bindEvents(){const{elements:t}=this;this.addEventListener(t.closeBtn,"click",()=>this.hide()),this.addEventListener(t.refreshBtn,"click",()=>this.refresh()),this.addEventListener(t.overlay,"click",e=>{e.target===t.overlay&&this.options.clickMaskClose&&this.hide()}),this.addEventListener(document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}),this.addEventListener(document,"visibilitychange",()=>this.handleVisibilityChange()),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:t=>this.handleStart(t),move:this.throttledHandleMove,end:()=>this.handleEnd()};this.addEventListener(t.btn,"mousedown",e.start),this.addEventListener(t.btn,"touchstart",e.start),this.addEventListener(t.sliderImg,"mousedown",e.start),this.addEventListener(t.sliderImg,"touchstart",e.start),this.addEventListener(document,"mousemove",e.move,{passive:!1}),this.addEventListener(document,"touchmove",e.move,{passive:!1}),this.addEventListener(document,"mouseup",e.end),this.addEventListener(document,"touchend",e.end)}addEventListener(t,e,s,i={}){t&&"function"==typeof s&&(t.addEventListener(e,s,i),this.eventListeners.push({element:t,event:e,handler:s,options:i}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:s,options:i})=>{try{t?.removeEventListener?.(e,s,i)}catch(t){}}),this.eventListeners.length=0}getDimensions(){if(!this.cachedDimensions){const t=this.elements.track.offsetWidth,e=this.elements.btn.offsetWidth;this.cachedDimensions={trackWidth:t,btnWidth:e,maxX:t-e}}return this.cachedDimensions}getPosition(){const{maxX:t}=this.getDimensions(),e=this.state.currentX/t;return Math.round(e*(this.options.width-this.options.sliderSize))}handleStart(t){!this.captchaData||this.state.isDragging||this.state.isLoading||(t.preventDefault(),this.state.isDragging=!0,this.state.startX=this.getClientX(t)-this.state.currentX,this.dragStartTime=Date.now(),this.times=[{time:Date.now(),position:this.getPosition()}],this.setTransition(!1),this.updateUIState("dragging"),this.cachedDimensions=null)}handleMove(t){if(!this.state.isDragging)return;t.preventDefault();const e=this.getClientX(t)-this.state.startX,{maxX:s}=this.getDimensions();this.state.currentX=Math.max(0,Math.min(e,s)),this.times.push({time:Date.now(),position:this.getPosition()}),this.rafId&&cancelAnimationFrame(this.rafId),this.rafId=requestAnimationFrame(()=>this.updateSliderPosition())}handleEnd(){this.state.isDragging&&(this.times.push({time:Date.now(),position:this.getPosition()}),this.state.isDragging=!1,this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.verify())}handleVisibilityChange(){const t=document.hidden?"paused":"running";this.elements.fingerAnimation&&(this.elements.fingerAnimation.style.animationPlayState=t)}getClientX(t){return t.type.includes("touch")?t.touches[0].clientX:t.clientX}setTransition(t){const e=t?"all 0.3s ease":"none";requestAnimationFrame(()=>{this.elements.btn.style.transition=e,this.elements.sliderImg.style.transition=e})}updateUIState(t){const{elements:e}=this,s={dragging:()=>{e.hint.style.opacity="0",e.fingerAnimation.style.display="none"},success:()=>{Object.assign(e.btn.style,{background:"var(--sc-success)"}),Object.assign(e.icon.style,{innerHTML:"✓",color:"white"}),e.icon.innerHTML="✓"},fail:()=>{Object.assign(e.btn.style,{background:"var(--sc-danger)"}),Object.assign(e.icon.style,{innerHTML:"✗",color:"white"}),e.icon.innerHTML="✗"},reset:()=>{Object.assign(e.btn.style,{background:"white"}),Object.assign(e.icon.style,{color:"#666"}),e.icon.innerHTML="→",e.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","var(--sc-text-light)")},loading:()=>{e.hint.style.opacity="0",e.fingerAnimation.style.display="none",Object.assign(e.track.style,{pointerEvents:"none",opacity:"0.6"})}};s[t]&&requestAnimationFrame(()=>{s[t](),"loading"!==t&&Object.assign(e.track.style,{pointerEvents:"auto",opacity:"1"})})}updateHintText(t,e){requestAnimationFrame(()=>{Object.assign(this.elements.hint,{textContent:t}),Object.assign(this.elements.hint.style,{color:e,opacity:"1"})})}updateSliderPosition(){const{elements:t,options:e,state:s}=this,{maxX:i}=this.getDimensions(),a=s.currentX/i*(e.width-e.sliderSize),r=s.currentX/i;requestAnimationFrame(()=>{t.btn.style.transform=`translateX(${s.currentX}px)`,t.sliderImg.style.transform=`translateX(${a}px)`,t.fingerAnimation.style.opacity=.8>r?"0.6":"0"})}show(){this.state.isVisible=!0,this.elements.overlay.style.display="flex",requestAnimationFrame(()=>{this.elements.overlay.classList.add("show"),this.elements.modal.classList.add("show")}),this.loadCaptcha()}hide(){this.state.isVisible=!1,this.elements.overlay.classList.remove("show"),this.elements.modal.classList.remove("show"),this.safeSetTimeout(()=>{this.elements.overlay.style.display="none",document.body.removeChild(this.elements.overlay),this.reset(),this.options.onClose?.(),this.elements.overlay=null},300)}handleError(t,e,s=null){const i={[PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR]:"网络连接失败,请检查网络设置",[PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR]:"请求超时,请重试",[PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR]:"验证失败,请重试",[PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR]:"图片加载失败,请刷新重试",[PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR]:"验证码数据错误,请刷新重试"}[t]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:i,originalError:s}),this.showError(i)}async loadCaptcha(){try{this.showLoading(),this.startTime=Date.now(),this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const t=await fetch(this.options.apiUrl,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({timestamp:Date.now(),...this.options.requestData}),signal:this.abortController.signal});if(!t.ok)throw Error(`HTTP ${t.status}: ${t.statusText}`);const e=await t.json();if(!this.validateCaptchaData(e))throw Error("验证码数据格式错误");this.captchaData=e.data,this.showCaptcha(),await this.renderCaptcha()}catch(t){"AbortError"===t.name?this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR,"请求被取消"):t.message.includes("Failed to fetch")||t.message.includes("NetworkError")?this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR,"网络连接失败"):this.handleError(PopupSliderCaptcha.ERROR_TYPES.CAPTCHA_DATA_ERROR,t.message,t)}}validateCaptchaData(t){if(!t||"object"!=typeof t)return!1;const e=t.data||t;return["canvasSrc","blockSrc","canvasWidth","canvasHeight","blockWidth","blockHeight","blockY","nonceStr"].every(t=>{const s=e[t];return null!=s&&""!==s})}async renderCaptcha(){return new Promise((t,e)=>{let s=!1;const i=[this.loadImageAsync(this.elements.backgroundImg,this.captchaData.canvasSrc,{width:this.captchaData.canvasWidth,height:this.captchaData.canvasHeight}),this.loadImageAsync(this.elements.sliderImg,this.captchaData.blockSrc,{width:this.captchaData.blockWidth,height:this.captchaData.blockHeight,top:this.captchaData.blockY})];Promise.all(i).then(()=>{s||(this.hideLoading(),t())}).catch(t=>{s||(s=!0,this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR,"图片加载失败",t),e(t))})})}async loadImageAsync(t,e,s){return new Promise((i,a)=>{if(this.imageCache.has(e)){const a=this.imageCache.get(e);return t.src=a.src,this.applyStyles(t,s),void i()}const r=this.safeSetTimeout(()=>{a(Error("图片加载超时"))},1e4);t.onload=()=>{this.safeClearTimeout(r),this.imageCache.set(e,t.cloneNode()),i()},t.onerror=t=>{this.safeClearTimeout(r),a(t)},t.src=e,this.applyStyles(t,s)})}applyStyles(t,e){Object.entries(e).forEach(([e,s])=>{t.style[e]="number"==typeof s?s+"px":s})}showLoading(){this.state.isLoading=!0,this.batchUpdateStyles({container:{display:"block"},loadingText:{display:"flex"},error:{display:"none"}}),this.updateUIState("loading")}hideLoading(){this.state.isLoading=!1,this.batchUpdateStyles({loadingText:{display:"none"}}),this.updateUIState("reset")}showCaptcha(){this.batchUpdateStyles({container:{display:"block"},track:{display:"block"},error:{display:"none"}})}showError(t){this.hideLoading(),this.batchUpdateStyles({error:{display:"block",textContent:t}})}batchUpdateStyles(t){requestAnimationFrame(()=>{Object.entries(t).forEach(([t,e])=>{const s=this.elements[t];s&&Object.entries(e).forEach(([t,e])=>{"textContent"===t?s.textContent=e:s.style[t]=e})})})}async verify(){if(this.captchaData)try{this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const t=await fetch(this.options.verifyUrl,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({loginVo:{nonceStr:this.captchaData.nonceStr,value:this.getPosition()},dragEventList:[...this.times],...this.options.verifyData}),signal:this.abortController.signal});if(!t.ok)throw Error(`HTTP ${t.status}: ${t.statusText}`);const e=await t.json();this.isVerifySuccess(e)?this.onVerifySuccess(e.data||e.result):this.onVerifyFail(e.message||e.msg||"验证失败,请重试!")}catch(t){"AbortError"===t.name?this.handleError(PopupSliderCaptcha.ERROR_TYPES.TIMEOUT_ERROR,"验证请求被取消"):t.message.includes("Failed to fetch")||t.message.includes("NetworkError")?this.handleError(PopupSliderCaptcha.ERROR_TYPES.NETWORK_ERROR,"网络连接失败"):this.handleError(PopupSliderCaptcha.ERROR_TYPES.VALIDATION_ERROR,t.message,t)}else this.onVerifyFail("验证码数据丢失")}isVerifySuccess(t){return!(!t||"object"!=typeof t)&&["0"===t.code,0===t.code,!0===t.success,"success"===t.status,!0===t.result].some(t=>!0===t)}onVerifySuccess(t){const e=this.dragStartTime?Date.now()-this.dragStartTime:Date.now()-this.startTime,s=`验证成功!耗时:${(e/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showFloatingTime(s,"success"),this.safeSetTimeout(()=>{this.options.onSuccess?.({ticket:t,timestamp:Date.now(),duration:e}),this.hide()},2e3)}showFloatingTime(t,e="success"){const{elements:s}=this;s.floatingTime.textContent=t,s.floatingTime.className="slider-captcha-floating-time "+e,this.safeSetTimeout(()=>s.floatingTime.classList.add("show"),100),this.safeSetTimeout(()=>{s.floatingTime.className="slider-captcha-floating-time"},2500)}onVerifyFail(t){this.state.retryCount++,this.updateUIState("fail"),this.showFloatingTime(t,"fail"),this.safeSetTimeout(()=>{this.state.retryCount<this.options.maxRetries?this.reset():this.refresh()},2500)}reset(){this.clearAllTimers(),Object.assign(this.state,{isDragging:!1,currentX:0,startX:0,isLoading:!1}),this.times=[],this.startTime=null,this.dragStartTime=null,this.cachedDimensions=null,requestAnimationFrame(()=>{this.setTransition(!0),this.elements.btn.style.transform="translateX(0px)",this.elements.sliderImg.style.transform="translateX(0px)",this.updateUIState("reset"),this.elements.error.style.display="none"})}refresh(){this.reset(),this.state.retryCount=0,this.loadCaptcha()}safeSetTimeout(t,e){const s=setTimeout(()=>{this.timers.delete(s),t()},e);return this.timers.add(s),s}safeClearTimeout(t){t&&(clearTimeout(t),this.timers.delete(t))}clearAllTimers(){this.timers.forEach(t=>{clearTimeout(t),clearInterval(t)}),this.timers.clear(),this.rafId&&(cancelAnimationFrame(this.rafId),this.rafId=null)}cleanupImages(){this.elements.backgroundImg&&(this.elements.backgroundImg.src="",this.elements.backgroundImg.onload=null,this.elements.backgroundImg.onerror=null),this.elements.sliderImg&&(this.elements.sliderImg.src="",this.elements.sliderImg.onload=null,this.elements.sliderImg.onerror=null),this.imageCache.clear()}throttle(t,e){let s=0;return function(...i){const a=Date.now();if(a-s>=e)return s=a,t.apply(this,i)}}destroy(){try{this.abortController&&(this.abortController.abort(),this.abortController=null),document.body&&(document.body.style.userSelect="",document.body.style.cursor=""),this.clearAllTimers(),this.removeAllEventListeners(),this.cleanupImages(),this.elements?.overlay?.parentNode&&this.elements.overlay.parentNode.removeChild(this.elements.overlay);const t=document.getElementById("slider-captcha-styles");t&&t.remove(),this.imageCache.clear(),this.cachedDimensions=null,Object.keys(this).forEach(t=>{"constructor"!==t&&(this[t]=null)}),this.options.onDestroy&&this.options.onDestroy()}catch(t){}}static create(t){return new PopupSliderCaptcha(t)}static show(t){const e=new PopupSliderCaptcha(t);return e.show(),e}}"undefined"!=typeof module&&module.exports?(module.exports=PopupSliderCaptcha,module.exports.default=PopupSliderCaptcha):"function"==typeof define&&define.amd?define([],()=>PopupSliderCaptcha):"undefined"!=typeof window&&(window.PopupSliderCaptcha=PopupSliderCaptcha,window.SliderCaptcha=PopupSliderCaptcha),i=PopupSliderCaptcha;class PasswordValidator{static CONSTANTS={DEFAULT_TIMEOUT:1e4,CACHE_DURATION:3e5,MIN_PASSWORD_LENGTH:1};static ERROR_TYPES={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",ENCRYPTION_ERROR:"ENCRYPTION_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",PUBLIC_KEY_ERROR:"PUBLIC_KEY_ERROR"};constructor(t={}){this.options={publicKeyUrl:t.publicKeyUrl||"/microservice/strongPassword/getPublicKey",validateUrl:t.validateUrl||"/microservice/strongPassword/checkPassword",timeout:t.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:t.headers||{},cacheDuration:t.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...t},this.publicKeyCache=null,this.publicKeyExpiry=null}async getPublicKey(){if(this.publicKeyCache&&this.publicKeyExpiry&&Date.now()<this.publicKeyExpiry)return this.publicKeyCache;try{const t=await this.makeRequest(this.options.publicKeyUrl,{method:"GET"});if(!this.isSuccessResponse(t))throw Error("获取公钥失败:"+(t.message||t.msg||"未知错误"));const e=t.data||t.result;if(!e)throw Error("公钥为空");return this.publicKeyCache=e,this.publicKeyExpiry=Date.now()+this.options.cacheDuration,this.publicKeyCache}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR,t.message,t),Error("获取公钥失败: "+t.message)}}isSuccessResponse(t){return!(!t||"object"!=typeof t)&&["0"===t.code,0===t.code,!0===t.success,"success"===t.status,!0===t.result].some(t=>!0===t)}handleError(t,e,s=null){const i={[PasswordValidator.ERROR_TYPES.NETWORK_ERROR]:"网络连接失败,请检查网络设置",[PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]:"请求超时,请重试",[PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]:"密码加密失败",[PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]:"密码验证失败",[PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]:"获取公钥失败"}[t]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:i,originalError:s})}encryptPassword(t,e){try{if(!t)throw Error("密码不能为空");if(t.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!e)throw Error("公钥不能为空");if(void 0===JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const s=new JSEncrypt;s.setPublicKey(e);const i=s.encrypt(t);if(!i)throw Error("密码加密失败,可能是公钥格式不正确");return i}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,e,s={}){try{if(!t||!e)throw Error("用户名密码不能为空");const i=await this.getPublicKey(),a=this.encryptPassword(t,i),r=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:e,password:a,timestamp:Date.now(),...s})});return{success:this.isSuccessResponse(r),data:r.data||r.result,message:r.message||r.msg||"验证完成",code:r.code||r.status,originalResponse:r}}catch(t){let e=PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;return t.message.includes("网络")||t.message.includes("fetch")?e=PasswordValidator.ERROR_TYPES.NETWORK_ERROR:t.message.includes("超时")||t.message.includes("timeout")?e=PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR:t.message.includes("加密")&&(e=PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR),this.handleError(e,t.message,t),{success:!1,message:t.message||"密码校验失败",code:e,error:t}}}async makeRequest(t,e={}){const s=new AbortController,i=setTimeout(()=>{s.abort()},this.options.timeout);try{const a=await fetch(t,{...e,headers:{"Content-Type":"application/json",...this.options.headers,...e.headers},signal:s.signal});if(clearTimeout(i),!a.ok)throw Error(`HTTP错误: ${a.status} ${a.statusText}`);const r=await a.json();return this.options.debug,r}catch(t){if(clearTimeout(i),"AbortError"===t.name)throw this.handleError(PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR,"请求超时",t),Error("请求超时");if(t.message.includes("Failed to fetch")||t.message.includes("NetworkError"))throw this.handleError(PasswordValidator.ERROR_TYPES.NETWORK_ERROR,"网络连接失败",t),Error("网络连接失败");throw t}}clearCache(){this.publicKeyCache=null,this.publicKeyExpiry=null}updateOptions(t){this.options={...this.options,...t},this.clearCache()}cancelRequest(){}isRequesting(){return!1}getCacheStatus(){return{hasCache:!!this.publicKeyCache,isExpired:!this.publicKeyExpiry||Date.now()>this.publicKeyExpiry,expiryTime:this.publicKeyExpiry,remainingTime:this.publicKeyExpiry?Math.max(0,this.publicKeyExpiry-Date.now()):0}}destroy(){try{this.cancelRequest(),this.clearCache(),this.options.onDestroy&&this.options.onDestroy()}catch(t){}}}r={PopupSliderCaptcha:i,PasswordValidator:a=PasswordValidator,createPasswordValidator:e,validatePassword:s},"undefined"!=typeof window&&(window.SliderCaptcha=i,window.PopupSliderCaptcha=i,window.PasswordValidator=a,window.createPasswordValidator=e,window.validatePassword=s),t.PasswordValidator=a,t.PopupSliderCaptcha=i,t.createPasswordValidator=e,t.default=r,t.validatePassword=s,Object.defineProperty(t,"__esModule",{value:!0})});
|
|
@@ -9,33 +9,30 @@ class PasswordValidator {
|
|
|
9
9
|
static CONSTANTS = {
|
|
10
10
|
DEFAULT_TIMEOUT: 10000,
|
|
11
11
|
CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
|
|
12
|
-
MIN_PASSWORD_LENGTH: 1
|
|
12
|
+
MIN_PASSWORD_LENGTH: 1,
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// 优化:添加错误类型枚举
|
|
16
15
|
static ERROR_TYPES = {
|
|
17
16
|
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
18
17
|
TIMEOUT_ERROR: 'TIMEOUT_ERROR',
|
|
19
18
|
ENCRYPTION_ERROR: 'ENCRYPTION_ERROR',
|
|
20
19
|
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
21
|
-
PUBLIC_KEY_ERROR: 'PUBLIC_KEY_ERROR'
|
|
20
|
+
PUBLIC_KEY_ERROR: 'PUBLIC_KEY_ERROR',
|
|
22
21
|
}
|
|
23
22
|
|
|
24
23
|
constructor(options = {}) {
|
|
25
|
-
// 优化:合并配置,使用常量
|
|
26
24
|
this.options = {
|
|
27
25
|
publicKeyUrl: options.publicKeyUrl || '/microservice/strongPassword/getPublicKey',
|
|
28
26
|
validateUrl: options.validateUrl || '/microservice/strongPassword/checkPassword',
|
|
29
27
|
timeout: options.timeout || PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,
|
|
30
28
|
headers: options.headers || {},
|
|
31
29
|
cacheDuration: options.cacheDuration || PasswordValidator.CONSTANTS.CACHE_DURATION,
|
|
32
|
-
...options
|
|
30
|
+
...options,
|
|
33
31
|
};
|
|
34
32
|
|
|
35
33
|
// 缓存公钥,避免重复请求
|
|
36
34
|
this.publicKeyCache = null;
|
|
37
35
|
this.publicKeyExpiry = null;
|
|
38
|
-
this.abortController = null;
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
/**
|
|
@@ -49,15 +46,8 @@ class PasswordValidator {
|
|
|
49
46
|
}
|
|
50
47
|
|
|
51
48
|
try {
|
|
52
|
-
// 取消之前的请求
|
|
53
|
-
if (this.abortController) {
|
|
54
|
-
this.abortController.abort();
|
|
55
|
-
}
|
|
56
|
-
this.abortController = new AbortController();
|
|
57
|
-
|
|
58
49
|
const response = await this.makeRequest(this.options.publicKeyUrl, {
|
|
59
50
|
method: 'GET',
|
|
60
|
-
signal: this.abortController.signal
|
|
61
51
|
});
|
|
62
52
|
|
|
63
53
|
if (!this.isSuccessResponse(response)) {
|
|
@@ -66,8 +56,8 @@ class PasswordValidator {
|
|
|
66
56
|
|
|
67
57
|
// 验证公钥格式
|
|
68
58
|
const publicKey = response.data || response.result;
|
|
69
|
-
if (!
|
|
70
|
-
throw new Error('
|
|
59
|
+
if (!publicKey) {
|
|
60
|
+
throw new Error('公钥为空')
|
|
71
61
|
}
|
|
72
62
|
|
|
73
63
|
// 缓存公钥
|
|
@@ -84,35 +74,28 @@ class PasswordValidator {
|
|
|
84
74
|
|
|
85
75
|
// 优化:添加响应成功判断方法
|
|
86
76
|
isSuccessResponse(response) {
|
|
87
|
-
if (!response || typeof response !== 'object')
|
|
77
|
+
if (!response || typeof response !== 'object') {
|
|
78
|
+
return false
|
|
79
|
+
}
|
|
88
80
|
|
|
89
81
|
const successIndicators = [
|
|
90
82
|
response.code === '0',
|
|
91
83
|
response.code === 0,
|
|
92
84
|
response.success === true,
|
|
93
85
|
response.status === 'success',
|
|
94
|
-
response.result === true
|
|
86
|
+
response.result === true,
|
|
95
87
|
];
|
|
96
88
|
|
|
97
|
-
return successIndicators.some(indicator => indicator === true)
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 优化:添加公钥验证方法
|
|
101
|
-
validatePublicKey(publicKey) {
|
|
102
|
-
if (!publicKey || typeof publicKey !== 'string') return false
|
|
103
|
-
|
|
104
|
-
// 基本的RSA公钥格式检查
|
|
105
|
-
return true
|
|
89
|
+
return successIndicators.some((indicator) => indicator === true)
|
|
106
90
|
}
|
|
107
91
|
|
|
108
|
-
// 优化:添加统一错误处理方法
|
|
109
92
|
handleError(errorType, message, originalError = null) {
|
|
110
93
|
const errorMessages = {
|
|
111
94
|
[PasswordValidator.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
|
|
112
95
|
[PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]: '请求超时,请重试',
|
|
113
96
|
[PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]: '密码加密失败',
|
|
114
97
|
[PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]: '密码验证失败',
|
|
115
|
-
[PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]: '获取公钥失败'
|
|
98
|
+
[PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]: '获取公钥失败',
|
|
116
99
|
};
|
|
117
100
|
|
|
118
101
|
const errorMessage = errorMessages[errorType] || message || '未知错误';
|
|
@@ -122,7 +105,7 @@ class PasswordValidator {
|
|
|
122
105
|
this.options.onError({
|
|
123
106
|
type: errorType,
|
|
124
107
|
message: errorMessage,
|
|
125
|
-
originalError
|
|
108
|
+
originalError,
|
|
126
109
|
});
|
|
127
110
|
}
|
|
128
111
|
|
|
@@ -137,8 +120,7 @@ class PasswordValidator {
|
|
|
137
120
|
*/
|
|
138
121
|
encryptPassword(password, publicKey) {
|
|
139
122
|
try {
|
|
140
|
-
|
|
141
|
-
if (!password || typeof password !== 'string') {
|
|
123
|
+
if (!password) {
|
|
142
124
|
throw new Error('密码不能为空')
|
|
143
125
|
}
|
|
144
126
|
|
|
@@ -146,11 +128,10 @@ class PasswordValidator {
|
|
|
146
128
|
throw new Error('密码长度不足')
|
|
147
129
|
}
|
|
148
130
|
|
|
149
|
-
if (!publicKey
|
|
131
|
+
if (!publicKey) {
|
|
150
132
|
throw new Error('公钥不能为空')
|
|
151
133
|
}
|
|
152
134
|
|
|
153
|
-
// 使用导入的JSEncrypt进行RSA加密
|
|
154
135
|
if (typeof JSEncrypt === 'undefined') {
|
|
155
136
|
throw new Error('JSEncrypt库未正确加载,请确保已引入JSEncrypt库')
|
|
156
137
|
}
|
|
@@ -180,13 +161,8 @@ class PasswordValidator {
|
|
|
180
161
|
*/
|
|
181
162
|
async validatePassword(password, userName, additionalData = {}) {
|
|
182
163
|
try {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
throw new Error('密码不能为空')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
if (!userName || typeof userName !== 'string') {
|
|
189
|
-
throw new Error('用户名不能为空')
|
|
164
|
+
if (!password || !userName) {
|
|
165
|
+
throw new Error('用户名密码不能为空')
|
|
190
166
|
}
|
|
191
167
|
|
|
192
168
|
// 1. 获取公钥
|
|
@@ -202,17 +178,16 @@ class PasswordValidator {
|
|
|
202
178
|
userName: userName,
|
|
203
179
|
password: encryptedPassword,
|
|
204
180
|
timestamp: Date.now(),
|
|
205
|
-
...additionalData
|
|
206
|
-
})
|
|
181
|
+
...additionalData,
|
|
182
|
+
}),
|
|
207
183
|
});
|
|
208
184
|
|
|
209
|
-
// 优化:统一返回格式
|
|
210
185
|
return {
|
|
211
186
|
success: this.isSuccessResponse(response),
|
|
212
187
|
data: response.data || response.result,
|
|
213
188
|
message: response.message || response.msg || '验证完成',
|
|
214
189
|
code: response.code || response.status,
|
|
215
|
-
originalResponse: response
|
|
190
|
+
originalResponse: response,
|
|
216
191
|
}
|
|
217
192
|
} catch (error) {
|
|
218
193
|
console.error('密码校验失败:', error);
|
|
@@ -233,7 +208,7 @@ class PasswordValidator {
|
|
|
233
208
|
success: false,
|
|
234
209
|
message: error.message || '密码校验失败',
|
|
235
210
|
code: errorType,
|
|
236
|
-
error: error
|
|
211
|
+
error: error,
|
|
237
212
|
}
|
|
238
213
|
}
|
|
239
214
|
}
|
|
@@ -245,14 +220,11 @@ class PasswordValidator {
|
|
|
245
220
|
* @returns {Promise<Object>} 响应结果
|
|
246
221
|
*/
|
|
247
222
|
async makeRequest(url, options = {}) {
|
|
248
|
-
//
|
|
249
|
-
|
|
250
|
-
this.abortController.abort();
|
|
251
|
-
}
|
|
252
|
-
this.abortController = new AbortController();
|
|
223
|
+
// 优化:为每个请求创建独立的AbortController,避免多个请求互相取消
|
|
224
|
+
const abortController = new AbortController();
|
|
253
225
|
|
|
254
226
|
const timeoutId = setTimeout(() => {
|
|
255
|
-
|
|
227
|
+
abortController.abort();
|
|
256
228
|
}, this.options.timeout);
|
|
257
229
|
|
|
258
230
|
try {
|
|
@@ -261,9 +233,9 @@ class PasswordValidator {
|
|
|
261
233
|
headers: {
|
|
262
234
|
'Content-Type': 'application/json',
|
|
263
235
|
...this.options.headers,
|
|
264
|
-
...options.headers
|
|
236
|
+
...options.headers,
|
|
265
237
|
},
|
|
266
|
-
signal:
|
|
238
|
+
signal: abortController.signal,
|
|
267
239
|
});
|
|
268
240
|
|
|
269
241
|
clearTimeout(timeoutId);
|
|
@@ -317,20 +289,23 @@ class PasswordValidator {
|
|
|
317
289
|
|
|
318
290
|
/**
|
|
319
291
|
* 取消当前请求
|
|
292
|
+
* 注意:由于现在每个请求使用独立的AbortController,此方法主要用于向后兼容
|
|
320
293
|
*/
|
|
321
294
|
cancelRequest() {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
295
|
+
// 由于现在每个请求使用独立的AbortController,无法直接取消所有请求
|
|
296
|
+
// 如果需要取消特定请求,建议在调用validatePassword时保存返回的Promise并手动处理
|
|
297
|
+
console.warn('cancelRequest方法已过时,现在每个请求使用独立的AbortController');
|
|
326
298
|
}
|
|
327
299
|
|
|
328
300
|
/**
|
|
329
301
|
* 检查是否正在请求中
|
|
330
302
|
* @returns {boolean} 是否正在请求
|
|
303
|
+
* 注意:由于现在每个请求使用独立的AbortController,此方法无法准确判断
|
|
331
304
|
*/
|
|
332
305
|
isRequesting() {
|
|
333
|
-
|
|
306
|
+
// 由于现在每个请求使用独立的AbortController,无法准确判断是否正在请求
|
|
307
|
+
console.warn('isRequesting方法已过时,现在每个请求使用独立的AbortController');
|
|
308
|
+
return false
|
|
334
309
|
}
|
|
335
310
|
|
|
336
311
|
/**
|
|
@@ -342,7 +317,7 @@ class PasswordValidator {
|
|
|
342
317
|
hasCache: !!this.publicKeyCache,
|
|
343
318
|
isExpired: this.publicKeyExpiry ? Date.now() > this.publicKeyExpiry : true,
|
|
344
319
|
expiryTime: this.publicKeyExpiry,
|
|
345
|
-
remainingTime: this.publicKeyExpiry ? Math.max(0, this.publicKeyExpiry - Date.now()) : 0
|
|
320
|
+
remainingTime: this.publicKeyExpiry ? Math.max(0, this.publicKeyExpiry - Date.now()) : 0,
|
|
346
321
|
}
|
|
347
322
|
}
|
|
348
323
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(
|
|
1
|
+
!function(e,s){"object"==typeof exports&&"undefined"!=typeof module?s(exports,require("jsencrypt")):"function"==typeof define&&define.amd?define(["exports","jsencrypt"],s):s((e="undefined"!=typeof globalThis?globalThis:e||self).PasswordValidator={},e.JSEncrypt)}(this,function(e,JSEncrypt){"use strict";class PasswordValidator{static CONSTANTS={DEFAULT_TIMEOUT:1e4,CACHE_DURATION:3e5,MIN_PASSWORD_LENGTH:1};static ERROR_TYPES={NETWORK_ERROR:"NETWORK_ERROR",TIMEOUT_ERROR:"TIMEOUT_ERROR",ENCRYPTION_ERROR:"ENCRYPTION_ERROR",VALIDATION_ERROR:"VALIDATION_ERROR",PUBLIC_KEY_ERROR:"PUBLIC_KEY_ERROR"};constructor(e={}){this.options={publicKeyUrl:e.publicKeyUrl||"/microservice/strongPassword/getPublicKey",validateUrl:e.validateUrl||"/microservice/strongPassword/checkPassword",timeout:e.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:e.headers||{},cacheDuration:e.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...e},this.publicKeyCache=null,this.publicKeyExpiry=null}async getPublicKey(){if(this.publicKeyCache&&this.publicKeyExpiry&&Date.now()<this.publicKeyExpiry)return this.publicKeyCache;try{const e=await this.makeRequest(this.options.publicKeyUrl,{method:"GET"});if(!this.isSuccessResponse(e))throw Error("获取公钥失败:"+(e.message||e.msg||"未知错误"));const s=e.data||e.result;if(!s)throw Error("公钥为空");return this.publicKeyCache=s,this.publicKeyExpiry=Date.now()+this.options.cacheDuration,this.publicKeyCache}catch(e){throw this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR,e.message,e),Error("获取公钥失败: "+e.message)}}isSuccessResponse(e){return!(!e||"object"!=typeof e)&&["0"===e.code,0===e.code,!0===e.success,"success"===e.status,!0===e.result].some(e=>!0===e)}handleError(e,s,t=null){const r={[PasswordValidator.ERROR_TYPES.NETWORK_ERROR]:"网络连接失败,请检查网络设置",[PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]:"请求超时,请重试",[PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]:"密码加密失败",[PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]:"密码验证失败",[PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]:"获取公钥失败"}[e]||s||"未知错误";this.options.onError&&this.options.onError({type:e,message:r,originalError:t})}encryptPassword(e,s){try{if(!e)throw Error("密码不能为空");if(e.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!s)throw Error("公钥不能为空");if(void 0===JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const t=new JSEncrypt;t.setPublicKey(s);const r=t.encrypt(e);if(!r)throw Error("密码加密失败,可能是公钥格式不正确");return r}catch(e){throw this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,e.message,e),Error("密码加密失败: "+e.message)}}async validatePassword(e,s,t={}){try{if(!e||!s)throw Error("用户名密码不能为空");const r=await this.getPublicKey(),i=this.encryptPassword(e,r),o=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:s,password:i,timestamp:Date.now(),...t})});return{success:this.isSuccessResponse(o),data:o.data||o.result,message:o.message||o.msg||"验证完成",code:o.code||o.status,originalResponse:o}}catch(e){let s=PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;return e.message.includes("网络")||e.message.includes("fetch")?s=PasswordValidator.ERROR_TYPES.NETWORK_ERROR:e.message.includes("超时")||e.message.includes("timeout")?s=PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR:e.message.includes("加密")&&(s=PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR),this.handleError(s,e.message,e),{success:!1,message:e.message||"密码校验失败",code:s,error:e}}}async makeRequest(e,s={}){const t=new AbortController,r=setTimeout(()=>{t.abort()},this.options.timeout);try{const i=await fetch(e,{...s,headers:{"Content-Type":"application/json",...this.options.headers,...s.headers},signal:t.signal});if(clearTimeout(r),!i.ok)throw Error(`HTTP错误: ${i.status} ${i.statusText}`);const o=await i.json();return this.options.debug,o}catch(e){if(clearTimeout(r),"AbortError"===e.name)throw this.handleError(PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR,"请求超时",e),Error("请求超时");if(e.message.includes("Failed to fetch")||e.message.includes("NetworkError"))throw this.handleError(PasswordValidator.ERROR_TYPES.NETWORK_ERROR,"网络连接失败",e),Error("网络连接失败");throw e}}clearCache(){this.publicKeyCache=null,this.publicKeyExpiry=null}updateOptions(e){this.options={...this.options,...e},this.clearCache()}cancelRequest(){}isRequesting(){return!1}getCacheStatus(){return{hasCache:!!this.publicKeyCache,isExpired:!this.publicKeyExpiry||Date.now()>this.publicKeyExpiry,expiryTime:this.publicKeyExpiry,remainingTime:this.publicKeyExpiry?Math.max(0,this.publicKeyExpiry-Date.now()):0}}destroy(){try{this.cancelRequest(),this.clearCache(),this.options.onDestroy&&this.options.onDestroy()}catch(e){}}}e.createPasswordValidator=function(e){return new PasswordValidator(e)},e.default=PasswordValidator,e.validatePassword=async function(e,s={},t={}){const r=new PasswordValidator(s),i=t.userName||"";return await r.validatePassword(e,i,t)},Object.defineProperty(e,"__esModule",{value:!0})});
|