slider-captcha-sdk 1.0.18 → 1.0.20

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.min.js CHANGED
@@ -1 +1 @@
1
- var t,i;t=this,i=function(t,JSEncrypt){"use strict";function i(t){return new PasswordValidator(t)}async function e(t,i={},e={}){const s=new PasswordValidator(i),r=e.userName||"";return await s.validatePassword(t,r,e)}var s,r,a;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){void 0,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:i}=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((([i,e,s,r])=>{t[i]=this.createElement(e,s,r)})),t.container.style.cssText=`width:${i.width}px;height:${i.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,i="",e=""){const s=document.createElement(t);return i&&(s.className=i),e&&(s.textContent=e),s}assembleDOM(){const{elements:t}=this,i=this.createElement("div","slider-captcha-header-buttons");i.append(t.refreshBtn,t.closeBtn),t.header.append(t.title,i),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",(i=>{i.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,i={start:t=>this.handleStart(t),move:this.throttledHandleMove,end:()=>this.handleEnd()};this.addEventListener(t.btn,"mousedown",i.start),this.addEventListener(t.btn,"touchstart",i.start),this.addEventListener(t.sliderImg,"mousedown",i.start),this.addEventListener(t.sliderImg,"touchstart",i.start),this.addEventListener(document,"mousemove",i.move,{passive:!1}),this.addEventListener(document,"touchmove",i.move,{passive:!1}),this.addEventListener(document,"mouseup",i.end),this.addEventListener(document,"touchend",i.end)}addEventListener(t,i,e,s={}){t&&"function"==typeof e&&(t.addEventListener(i,e,s),this.eventListeners.push({element:t,event:i,handler:e,options:s}))}removeAllEventListeners(){this.eventListeners.forEach((({element:t,event:i,handler:e,options:s})=>{try{t?.removeEventListener?.(i,e,s)}catch(t){void 0}})),this.eventListeners.length=0}getDimensions(){if(!this.cachedDimensions){const t=this.elements.track.offsetWidth,i=this.elements.btn.offsetWidth;this.cachedDimensions={trackWidth:t,btnWidth:i,maxX:t-i}}return this.cachedDimensions}getPosition(){const{maxX:t}=this.getDimensions(),i=this.state.currentX/t;return Math.round(i*(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 i=this.getClientX(t)-this.state.startX,{maxX:e}=this.getDimensions();this.state.currentX=Math.max(0,Math.min(i,e)),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 i=t?"all 0.3s ease":"none";requestAnimationFrame((()=>{this.elements.btn.style.transition=i,this.elements.sliderImg.style.transition=i}))}updateUIState(t){const{elements:i}=this,e={dragging:()=>{i.hint.style.opacity="0",i.fingerAnimation.style.display="none"},success:()=>{Object.assign(i.btn.style,{background:"var(--sc-success)"}),Object.assign(i.icon.style,{innerHTML:"✓",color:"white"}),i.icon.innerHTML="✓"},fail:()=>{Object.assign(i.btn.style,{background:"var(--sc-danger)"}),Object.assign(i.icon.style,{innerHTML:"✗",color:"white"}),i.icon.innerHTML="✗"},reset:()=>{Object.assign(i.btn.style,{background:"white"}),Object.assign(i.icon.style,{color:"#666"}),i.icon.innerHTML="→",i.fingerAnimation.style.display="block",this.updateHintText("向右滑动完成验证","var(--sc-text-light)")},loading:()=>{i.hint.style.opacity="0",i.fingerAnimation.style.display="none",Object.assign(i.track.style,{pointerEvents:"none",opacity:"0.6"})}};e[t]&&requestAnimationFrame((()=>{e[t](),"loading"!==t&&Object.assign(i.track.style,{pointerEvents:"auto",opacity:"1"})}))}updateHintText(t,i){requestAnimationFrame((()=>{Object.assign(this.elements.hint,{textContent:t}),Object.assign(this.elements.hint.style,{color:i,opacity:"1"})}))}updateSliderPosition(){const{elements:t,options:i,state:e}=this,{maxX:s}=this.getDimensions(),r=e.currentX/s*(i.width-i.sliderSize),a=e.currentX/s;requestAnimationFrame((()=>{t.btn.style.transform=`translateX(${e.currentX}px)`,t.sliderImg.style.transform=`translateX(${r}px)`,t.fingerAnimation.style.opacity=.8>a?"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,i,e=null){const s={[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]||i||"未知错误";this.options.onError&&this.options.onError({type:t,message:s,originalError:e}),this.showError(s)}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 i=await t.json();if(!this.validateCaptchaData(i))throw Error("验证码数据格式错误");this.captchaData=i.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 i=t.data||t;return["canvasSrc","blockSrc","canvasWidth","canvasHeight","blockWidth","blockHeight","blockY","nonceStr"].every((t=>{const e=i[t];return null!=e&&""!==e}))}async renderCaptcha(){return new Promise(((t,i)=>{let e=!1;const s=[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(s).then((()=>{e||(this.hideLoading(),t())})).catch((t=>{e||(e=!0,this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR,"图片加载失败",t),i(t))}))}))}async loadImageAsync(t,i,e){return new Promise(((s,r)=>{if(this.imageCache.has(i)){const r=this.imageCache.get(i);return t.src=r.src,this.applyStyles(t,e),s(),void 0}const a=this.safeSetTimeout((()=>{r(Error("图片加载超时"))}),1e4);t.onload=()=>{this.safeClearTimeout(a),this.imageCache.set(i,t.cloneNode()),s()},t.onerror=t=>{this.safeClearTimeout(a),r(t)},t.src=i,this.applyStyles(t,e)}))}applyStyles(t,i){Object.entries(i).forEach((([i,e])=>{t.style[i]="number"==typeof e?e+"px":e}))}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,i])=>{const e=this.elements[t];e&&Object.entries(i).forEach((([t,i])=>{"textContent"===t?e.textContent=i:e.style[t]=i}))}))}))}async verify(){if(!this.captchaData)return this.onVerifyFail("验证码数据丢失"),void 0;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 i=await t.json();this.isVerifySuccess(i)?this.onVerifySuccess(i.data||i.result):this.onVerifyFail(i.message||i.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)}}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 i=this.dragStartTime?Date.now()-this.dragStartTime:Date.now()-this.startTime,e=`验证成功!耗时:${(i/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showFloatingTime(e,"success"),this.safeSetTimeout((()=>{this.options.onSuccess?.({ticket:t,timestamp:Date.now(),duration:i}),this.hide()}),2e3)}showFloatingTime(t,i="success"){const{elements:e}=this;e.floatingTime.textContent=t,e.floatingTime.className="slider-captcha-floating-time "+i,this.safeSetTimeout((()=>e.floatingTime.classList.add("show")),100),this.safeSetTimeout((()=>{e.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,i){const e=setTimeout((()=>{this.timers.delete(e),t()}),i);return this.timers.add(e),e}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,i){let e=0;return function(...s){const r=Date.now();if(r-e>=i)return e=r,t.apply(this,s)}}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){void 0}}static create(t){return new PopupSliderCaptcha(t)}static show(t){const i=new PopupSliderCaptcha(t);return i.show(),i}}"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),s=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 i=t.data||t.result;if(!this.validatePublicKey(i))throw Error("公钥格式无效");return this.publicKeyCache=i,this.publicKeyExpiry=Date.now()+this.options.cacheDuration,this.publicKeyCache}catch(t){throw void 0,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,i,e=null){const s={[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]||i||"未知错误";this.options.onError&&this.options.onError({type:t,message:s,originalError:e})}encryptPassword(t,i){try{if(!t)throw Error("密码不能为空");if(t.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!i)throw Error("公钥不能为空");if("undefined"==typeof JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const e=new JSEncrypt;e.setPublicKey(i);const s=e.encrypt(t);if(!s)throw Error("密码加密失败,可能是公钥格式不正确");return s}catch(t){throw void 0,this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,i,e={}){try{if(!t)throw Error("密码不能为空");if(!i)throw Error("用户名不能为空");const s=await this.getPublicKey(),r=this.encryptPassword(t,s),a=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:i,password:r,timestamp:Date.now(),...e})});return{success:this.isSuccessResponse(a),data:a.data||a.result,message:a.message||a.msg||"验证完成",code:a.code||a.status,originalResponse:a}}catch(t){void 0;let i=PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;return t.message.includes("网络")||t.message.includes("fetch")?i=PasswordValidator.ERROR_TYPES.NETWORK_ERROR:t.message.includes("超时")||t.message.includes("timeout")?i=PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR:t.message.includes("加密")&&(i=PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR),this.handleError(i,t.message,t),{success:!1,message:t.message||"密码校验失败",code:i,error:t}}}async makeRequest(t,i={}){this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const e=setTimeout((()=>{this.abortController.abort()}),this.options.timeout);try{const s=await fetch(t,{...i,headers:{"Content-Type":"application/json",...this.options.headers,...i.headers},signal:this.abortController.signal});if(clearTimeout(e),!s.ok)throw Error(`HTTP错误: ${s.status} ${s.statusText}`);const r=await s.json();return this.options.debug,0,r}catch(t){if(clearTimeout(e),"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){void 0}}}a={PopupSliderCaptcha:s,PasswordValidator:r=PasswordValidator,createPasswordValidator:i,validatePassword:e},"undefined"!=typeof window&&(window.SliderCaptcha=s,window.PopupSliderCaptcha=s,window.PasswordValidator=r,window.createPasswordValidator=i,window.validatePassword=e),t.PasswordValidator=r,t.PopupSliderCaptcha=s,t.createPasswordValidator=i,t.default=a,t.validatePassword=e,Object.defineProperty(t,"t",{value:!0})},void("object"==typeof exports&&"undefined"!=typeof module?i(exports,require("jsencrypt")):"function"==typeof define&&define.amd?define(["exports","jsencrypt"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).SliderCaptchaSDK={},t.JSEncrypt));
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)}function s(t,e,s={},i={}){return new PasswordValidator(s).validatePassword(t,e,i)}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=function(){this.safeClearTimeout(r),this.imageCache.set(e,t.cloneNode()),i()}.bind(this),t.onerror=function(t){this.safeClearTimeout(r),a(t)}.bind(this),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(...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()}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.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})});
@@ -4,16 +4,16 @@ declare module 'slider-captcha-sdk/password-validator' {
4
4
  // API配置
5
5
  publicKeyUrl?: string
6
6
  validateUrl?: string
7
-
7
+
8
8
  // 请求配置
9
9
  timeout?: number
10
10
  headers?: Record<string, string>
11
11
  retryAttempts?: number
12
12
  cacheDuration?: number
13
-
13
+
14
14
  // 调试配置
15
15
  debug?: boolean
16
-
16
+
17
17
  // 回调函数
18
18
  onError?: (error: ErrorInfo) => void
19
19
  onDestroy?: () => void
@@ -63,31 +63,27 @@ declare module 'slider-captcha-sdk/password-validator' {
63
63
 
64
64
  export default class PasswordValidator {
65
65
  constructor(options?: PasswordValidatorOptions)
66
-
66
+
67
67
  // 核心方法
68
68
  getPublicKey(): Promise<string>
69
69
  encryptPassword(password: string, publicKey: string): string
70
70
  validatePassword(password: string, userName: string, additionalData?: Record<string, any>): Promise<ValidationResult>
71
-
71
+
72
72
  // 缓存管理
73
73
  clearCache(): void
74
74
  getCacheStatus(): CacheStatus
75
-
75
+
76
76
  // 配置管理
77
77
  updateOptions(newOptions: Partial<PasswordValidatorOptions>): void
78
-
79
- // 请求控制
80
- cancelRequest(): void
81
- isRequesting(): boolean
82
-
78
+
83
79
  // 生命周期
84
80
  destroy(): void
85
-
81
+
86
82
  // 工具方法
87
83
  isSuccessResponse(response: any): boolean
88
84
  validatePublicKey(publicKey: string): boolean
89
85
  handleError(errorType: string, message: string, originalError?: Error): void
90
-
86
+
91
87
  // 常量
92
88
  static readonly CONSTANTS: Record<string, number>
93
89
  static readonly ERROR_TYPES: Record<string, string>
@@ -95,12 +91,12 @@ declare module 'slider-captcha-sdk/password-validator' {
95
91
 
96
92
  // 工厂函数
97
93
  export function createPasswordValidator(options?: PasswordValidatorOptions): PasswordValidator
98
-
94
+
99
95
  // 便捷函数
100
96
  export function validatePassword(
101
- password: string,
102
- userName: string,
103
- options?: PasswordValidatorOptions,
97
+ password: string,
98
+ userName: string,
99
+ options?: PasswordValidatorOptions,
104
100
  additionalData?: Record<string, any>
105
101
  ): Promise<ValidationResult>
106
102
  }
@@ -5,14 +5,12 @@ import JSEncrypt from 'jsencrypt';
5
5
  * 提供密码加密和校验功能
6
6
  */
7
7
  class PasswordValidator {
8
- // 优化:添加常量定义
9
8
  static CONSTANTS = {
10
9
  DEFAULT_TIMEOUT: 10000,
11
10
  CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
12
11
  MIN_PASSWORD_LENGTH: 1
13
12
  }
14
13
 
15
- // 优化:添加错误类型枚举
16
14
  static ERROR_TYPES = {
17
15
  NETWORK_ERROR: 'NETWORK_ERROR',
18
16
  TIMEOUT_ERROR: 'TIMEOUT_ERROR',
@@ -22,7 +20,6 @@ class PasswordValidator {
22
20
  }
23
21
 
24
22
  constructor(options = {}) {
25
- // 优化:合并配置,使用常量
26
23
  this.options = {
27
24
  publicKeyUrl: options.publicKeyUrl || '/microservice/strongPassword/getPublicKey',
28
25
  validateUrl: options.validateUrl || '/microservice/strongPassword/checkPassword',
@@ -35,7 +32,6 @@ class PasswordValidator {
35
32
  // 缓存公钥,避免重复请求
36
33
  this.publicKeyCache = null;
37
34
  this.publicKeyExpiry = null;
38
- this.abortController = null;
39
35
  }
40
36
 
41
37
  /**
@@ -49,25 +45,18 @@ class PasswordValidator {
49
45
  }
50
46
 
51
47
  try {
52
- // 取消之前的请求
53
- if (this.abortController) {
54
- this.abortController.abort();
55
- }
56
- this.abortController = new AbortController();
57
-
58
48
  const response = await this.makeRequest(this.options.publicKeyUrl, {
59
- method: 'GET',
60
- signal: this.abortController.signal
49
+ method: 'GET'
61
50
  });
62
51
 
63
52
  if (!this.isSuccessResponse(response)) {
64
- throw new Error('获取公钥失败:' + (response.message || response.msg || '未知错误'))
53
+ throw new Error(`获取公钥失败:${response.message || response.msg || '未知错误'}`)
65
54
  }
66
55
 
67
56
  // 验证公钥格式
68
57
  const publicKey = response.data || response.result;
69
- if (!this.validatePublicKey(publicKey)) {
70
- throw new Error('公钥格式无效')
58
+ if (!publicKey) {
59
+ throw new Error('公钥为空')
71
60
  }
72
61
 
73
62
  // 缓存公钥
@@ -78,13 +67,15 @@ class PasswordValidator {
78
67
  } catch (error) {
79
68
  console.error('获取公钥失败:', error);
80
69
  this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR, error.message, error);
81
- throw new Error('获取公钥失败: ' + error.message)
70
+ throw new Error(`获取公钥失败: ${error.message}`)
82
71
  }
83
72
  }
84
73
 
85
74
  // 优化:添加响应成功判断方法
86
75
  isSuccessResponse(response) {
87
- if (!response || typeof response !== 'object') return false
76
+ if (!response || typeof response !== 'object') {
77
+ return false
78
+ }
88
79
 
89
80
  const successIndicators = [
90
81
  response.code === '0',
@@ -94,18 +85,9 @@ class PasswordValidator {
94
85
  response.result === true
95
86
  ];
96
87
 
97
- return successIndicators.some(indicator => indicator === true)
88
+ return successIndicators.some((indicator) => indicator === true)
98
89
  }
99
90
 
100
- // 优化:添加公钥验证方法
101
- validatePublicKey(publicKey) {
102
- if (!publicKey || typeof publicKey !== 'string') return false
103
-
104
- // 基本的RSA公钥格式检查
105
- return true
106
- }
107
-
108
- // 优化:添加统一错误处理方法
109
91
  handleError(errorType, message, originalError = null) {
110
92
  const errorMessages = {
111
93
  [PasswordValidator.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
@@ -137,7 +119,6 @@ class PasswordValidator {
137
119
  */
138
120
  encryptPassword(password, publicKey) {
139
121
  try {
140
- // 优化:输入验证
141
122
  if (!password) {
142
123
  throw new Error('密码不能为空')
143
124
  }
@@ -150,7 +131,6 @@ class PasswordValidator {
150
131
  throw new Error('公钥不能为空')
151
132
  }
152
133
 
153
- // 使用导入的JSEncrypt进行RSA加密
154
134
  if (typeof JSEncrypt === 'undefined') {
155
135
  throw new Error('JSEncrypt库未正确加载,请确保已引入JSEncrypt库')
156
136
  }
@@ -167,7 +147,7 @@ class PasswordValidator {
167
147
  } catch (error) {
168
148
  console.error('密码加密失败:', error);
169
149
  this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR, error.message, error);
170
- throw new Error('密码加密失败: ' + error.message)
150
+ throw new Error(`密码加密失败: ${error.message}`)
171
151
  }
172
152
  }
173
153
 
@@ -180,13 +160,8 @@ class PasswordValidator {
180
160
  */
181
161
  async validatePassword(password, userName, additionalData = {}) {
182
162
  try {
183
- // 优化:输入验证
184
- if (!password ) {
185
- throw new Error('密码不能为空')
186
- }
187
-
188
- if (!userName) {
189
- throw new Error('用户名不能为空')
163
+ if (!password || !userName) {
164
+ throw new Error('用户名密码不能为空')
190
165
  }
191
166
 
192
167
  // 1. 获取公钥
@@ -199,14 +174,13 @@ class PasswordValidator {
199
174
  const response = await this.makeRequest(this.options.validateUrl, {
200
175
  method: 'POST',
201
176
  body: JSON.stringify({
202
- userName: userName,
177
+ userName,
203
178
  password: encryptedPassword,
204
179
  timestamp: Date.now(),
205
180
  ...additionalData
206
181
  })
207
182
  });
208
183
 
209
- // 优化:统一返回格式
210
184
  return {
211
185
  success: this.isSuccessResponse(response),
212
186
  data: response.data || response.result,
@@ -233,7 +207,7 @@ class PasswordValidator {
233
207
  success: false,
234
208
  message: error.message || '密码校验失败',
235
209
  code: errorType,
236
- error: error
210
+ error
237
211
  }
238
212
  }
239
213
  }
@@ -245,14 +219,11 @@ class PasswordValidator {
245
219
  * @returns {Promise<Object>} 响应结果
246
220
  */
247
221
  async makeRequest(url, options = {}) {
248
- // 优化:使用实例的abortController
249
- if (this.abortController) {
250
- this.abortController.abort();
251
- }
252
- this.abortController = new AbortController();
222
+ // 优化:为每个请求创建独立的AbortController,避免多个请求互相取消
223
+ const abortController = new AbortController();
253
224
 
254
225
  const timeoutId = setTimeout(() => {
255
- this.abortController.abort();
226
+ abortController.abort();
256
227
  }, this.options.timeout);
257
228
 
258
229
  try {
@@ -263,7 +234,7 @@ class PasswordValidator {
263
234
  ...this.options.headers,
264
235
  ...options.headers
265
236
  },
266
- signal: this.abortController.signal
237
+ signal: abortController.signal
267
238
  });
268
239
 
269
240
  clearTimeout(timeoutId);
@@ -315,31 +286,13 @@ class PasswordValidator {
315
286
  this.clearCache();
316
287
  }
317
288
 
318
- /**
319
- * 取消当前请求
320
- */
321
- cancelRequest() {
322
- if (this.abortController) {
323
- this.abortController.abort();
324
- this.abortController = null;
325
- }
326
- }
327
-
328
- /**
329
- * 检查是否正在请求中
330
- * @returns {boolean} 是否正在请求
331
- */
332
- isRequesting() {
333
- return this.abortController && !this.abortController.signal.aborted
334
- }
335
-
336
289
  /**
337
290
  * 获取缓存状态
338
291
  * @returns {Object} 缓存状态信息
339
292
  */
340
293
  getCacheStatus() {
341
294
  return {
342
- hasCache: !!this.publicKeyCache,
295
+ hasCache: Boolean(this.publicKeyCache),
343
296
  isExpired: this.publicKeyExpiry ? Date.now() > this.publicKeyExpiry : true,
344
297
  expiryTime: this.publicKeyExpiry,
345
298
  remainingTime: this.publicKeyExpiry ? Math.max(0, this.publicKeyExpiry - Date.now()) : 0
@@ -351,9 +304,6 @@ class PasswordValidator {
351
304
  */
352
305
  destroy() {
353
306
  try {
354
- // 取消所有进行中的请求
355
- this.cancelRequest();
356
-
357
307
  // 清除缓存
358
308
  this.clearCache();
359
309
 
@@ -379,15 +329,14 @@ function createPasswordValidator(options) {
379
329
  /**
380
330
  * 快速校验密码的便捷函数
381
331
  * @param {string} password 密码
332
+ * @param {string} userName 用户名
382
333
  * @param {Object} options 配置选项
383
334
  * @param {Object} additionalData 额外数据
384
335
  * @returns {Promise<Object>} 校验结果
385
336
  */
386
- async function validatePassword(password, options = {}, additionalData = {}) {
337
+ function validatePassword(password, userName, options = {}, additionalData = {}) {
387
338
  const validator = new PasswordValidator(options);
388
- // 修复:添加userName参数,可以从additionalData中提取或设为空字符串
389
- const userName = additionalData.userName || '';
390
- return await validator.validatePassword(password, userName, additionalData)
339
+ return validator.validatePassword(password, userName, additionalData)
391
340
  }
392
341
 
393
342
  export { createPasswordValidator, PasswordValidator as default, validatePassword };
@@ -1 +1 @@
1
- var t,r;t=this,r=function(t,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(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 r=t.data||t.result;if(!this.validatePublicKey(r))throw Error("公钥格式无效");return this.publicKeyCache=r,this.publicKeyExpiry=Date.now()+this.options.cacheDuration,this.publicKeyCache}catch(t){throw void 0,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,r,s=null){const e={[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]||r||"未知错误";this.options.onError&&this.options.onError({type:t,message:e,originalError:s})}encryptPassword(t,r){try{if(!t)throw Error("密码不能为空");if(t.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!r)throw Error("公钥不能为空");if("undefined"==typeof JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const s=new JSEncrypt;s.setPublicKey(r);const e=s.encrypt(t);if(!e)throw Error("密码加密失败,可能是公钥格式不正确");return e}catch(t){throw void 0,this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,r,s={}){try{if(!t)throw Error("密码不能为空");if(!r)throw Error("用户名不能为空");const e=await this.getPublicKey(),i=this.encryptPassword(t,e),o=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:r,password:i,timestamp:Date.now(),...s})});return{success:this.isSuccessResponse(o),data:o.data||o.result,message:o.message||o.msg||"验证完成",code:o.code||o.status,originalResponse:o}}catch(t){void 0;let r=PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;return t.message.includes("网络")||t.message.includes("fetch")?r=PasswordValidator.ERROR_TYPES.NETWORK_ERROR:t.message.includes("超时")||t.message.includes("timeout")?r=PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR:t.message.includes("加密")&&(r=PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR),this.handleError(r,t.message,t),{success:!1,message:t.message||"密码校验失败",code:r,error:t}}}async makeRequest(t,r={}){this.abortController&&this.abortController.abort(),this.abortController=new AbortController;const s=setTimeout((()=>{this.abortController.abort()}),this.options.timeout);try{const e=await fetch(t,{...r,headers:{"Content-Type":"application/json",...this.options.headers,...r.headers},signal:this.abortController.signal});if(clearTimeout(s),!e.ok)throw Error(`HTTP错误: ${e.status} ${e.statusText}`);const i=await e.json();return this.options.debug,0,i}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){void 0}}}t.createPasswordValidator=function(t){return new PasswordValidator(t)},t.default=PasswordValidator,t.validatePassword=async function(t,r={},s={}){const e=new PasswordValidator(r),i=s.userName||"";return await e.validatePassword(t,i,s)},Object.defineProperty(t,"t",{value:!0})},void("object"==typeof exports&&"undefined"!=typeof module?r(exports,require("jsencrypt")):"function"==typeof define&&define.amd?define(["exports","jsencrypt"],r):r((t="undefined"!=typeof globalThis?globalThis:t||self).PasswordValidator={},t.JSEncrypt));
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()}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.clearCache(),this.options.onDestroy&&this.options.onDestroy()}catch(e){}}}e.createPasswordValidator=function(e){return new PasswordValidator(e)},e.default=PasswordValidator,e.validatePassword=function(e,s,t={},r={}){return new PasswordValidator(t).validatePassword(e,s,r)},Object.defineProperty(e,"__esModule",{value:!0})});