slider-captcha-sdk 1.0.20 → 1.0.21

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
- !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})});
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 i(t,e,i={},s={}){return new PasswordValidator(i).validatePassword(t,e,s)}var s,r,a;class PopupSliderCaptcha{static DEFAULTS={width:320,height:155,sliderSize:38,maxRetries:3,timeout:3e4,apiUrl:"/externalapi/commonservice/captcha/get",verifyUrl:"/externalapi/commonservice/captcha/check",baseUrl:"",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 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},this.options.apiUrl=this.normalizeUrl(this.options.apiUrl,this.options.baseUrl),this.options.verifyUrl=this.normalizeUrl(this.options.verifyUrl,this.options.baseUrl);const{elements:e={},state:i=this.createInitialState(),captchaData:s=null,times:r=[],startTime:a=null,eventListeners:n=[],timers:o=new Set,rafId:h=null,cachedDimensions:c=null,imageCache:l=new Map,abortController:d=null}=this;Object.assign(this,{elements:e,state:i,captchaData:s,times:r,startTime:a,eventListeners:n,timers:o,rafId:h,cachedDimensions:c,imageCache:l,abortController:d}),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}}normalizeUrl(t,e){return t?/^https?:\/\//.test(t)||!e?t:(e.endsWith("/")?e:e+"/")+(t.startsWith("/")?t.substring(1):t):t}init(){this.injectStyles(),this.createElements(),this.bindEvents()}injectStyles(){if(document.querySelector("#slider-captcha-styles"))return;const t=Object.assign(document.createElement("style"),{id:"slider-captcha-styles",textContent:PopupSliderCaptcha.getStyles()});document.head.appendChild(t)}createElements(){const{elements:t,options:e}=this,i={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]};Object.entries(i).forEach(([e,[i,s,r]])=>{t[e]=this.createElement(i,s,r)}),t.container.style.cssText=`width:${e.width}px;height:${e.height}px`,t.refreshBtn.innerHTML='\n <svg viewBox="0 0 24 24">\n <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"/>\n </svg>\n ',this.assembleDOM(),this.setInitialState()}createElement(t,e="",i=""){return Object.assign(document.createElement(t),{className:e,textContent: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;[[t.closeBtn,"click",()=>this.hide()],[t.refreshBtn,"click",()=>this.refresh()],[t.overlay,"click",e=>{e.target===t.overlay&&this.options.clickMaskClose&&this.hide()}],[document,"keydown",t=>{"Escape"===t.key&&this.state.isVisible&&this.hide()}],[document,"visibilitychange",()=>this.handleVisibilityChange()]].forEach(([t,e,i])=>{this.addEventListener(t,e,i)}),this.bindSliderEvents()}bindSliderEvents(){const{elements:t}=this,e={start:t=>this.handleStart(t),move:this.throttledHandleMove,end:()=>this.handleEnd()};[[t.btn,"mousedown",e.start],[t.btn,"touchstart",e.start],[t.sliderImg,"mousedown",e.start],[t.sliderImg,"touchstart",e.start],[document,"mousemove",e.move,{passive:!1}],[document,"touchmove",e.move,{passive:!1}],[document,"mouseup",e.end],[document,"touchend",e.end]].forEach(([t,e,i,s])=>{this.addEventListener(t,e,i,s)})}addEventListener(t,e,i,s={}){t&&"function"==typeof i&&(t.addEventListener(e,i,s),this.eventListeners.push({element:t,event:e,handler:i,options:s}))}removeAllEventListeners(){this.eventListeners.forEach(({element:t,event:e,handler:i,options:s})=>{try{t?.removeEventListener?.(e,i,s)}catch(t){}}),this.eventListeners.length=0}getDimensions(){if(!this.cachedDimensions){const{track:t,btn:e}=this.elements;this.cachedDimensions={trackWidth:t.offsetWidth,btnWidth:e.offsetWidth,get maxX(){return this.trackWidth-this.btnWidth}}}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(),Object.assign(this.state,{isDragging:!0,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:i}=this.getDimensions();this.state.currentX=Math.max(0,Math.min(e,i)),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?.style&&(this.elements.fingerAnimation.style.animationPlayState=t)}getClientX=t=>t.type.includes("touch")?t.touches[0].clientX:t.clientX;setTransition(t){const e=t?"all 0.3s ease":"none";requestAnimationFrame(()=>{const{btn:t,sliderImg:i}=this.elements;t.style.transition=i.style.transition=e})}updateUIState(t){const{elements:e}=this,i={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,{color:"white"}),e.icon.textContent="✓"},fail:()=>{Object.assign(e.btn.style,{background:"var(--sc-danger)"}),Object.assign(e.icon.style,{color:"white"}),e.icon.textContent="✗"},reset:()=>{Object.assign(e.btn.style,{background:"white"}),Object.assign(e.icon.style,{color:"#666"}),e.icon.textContent="→",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"})}};i[t]&&requestAnimationFrame(()=>{i[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:i}=this,{maxX:s}=this.getDimensions(),r=i.currentX/s*(e.width-e.sliderSize),a=i.currentX/s;requestAnimationFrame(()=>{t.btn.style.transform=`translateX(${i.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,e,i=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]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:s,originalError:i}),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 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 i=e[t];return null!=i&&""!==i})}renderCaptcha(){return new Promise((t,e)=>{let i=!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(()=>{i||(this.hideLoading(),t())}).catch(t=>{i||(i=!0,this.handleError(PopupSliderCaptcha.ERROR_TYPES.IMAGE_LOAD_ERROR,"图片加载失败",t),e(t))})})}loadImageAsync(t,e,i){return new Promise((s,r)=>{if(this.imageCache.has(e)){const r=this.imageCache.get(e);return t.src=r.src,this.applyStyles(t,i),void s()}const a=this.safeSetTimeout(()=>{r(Error("图片加载超时"))},1e4);t.onload=function(){this.safeClearTimeout(a),this.imageCache.set(e,t.cloneNode()),s()}.bind(this),t.onerror=function(t){this.safeClearTimeout(a),r(t)}.bind(this),t.src=e,this.applyStyles(t,i)})}applyStyles(t,e){Object.entries(e).forEach(([e,i])=>{t.style[e]="number"==typeof i?i+"px":i})}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 i=this.elements[t];i&&Object.entries(e).forEach(([t,e])=>{"textContent"===t?i.textContent=e:i.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,i=`验证成功!耗时:${(e/1e3).toFixed(2)}s`;this.updateUIState("success"),this.showFloatingTime(i,"success"),this.safeSetTimeout(()=>{this.options.onSuccess?.({ticket:t,timestamp:Date.now(),duration:e}),this.hide()},2e3)}showFloatingTime(t,e="success"){const{elements:i}=this;i.floatingTime.textContent=t,i.floatingTime.className="slider-captcha-floating-time "+e,this.safeSetTimeout(()=>i.floatingTime.classList.add("show"),100),this.safeSetTimeout(()=>{i.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(){try{this.reset(),this.state.retryCount=0,this.loadCaptcha()}catch(t){}}safeSetTimeout(t,e){const i=setTimeout(()=>{this.timers.delete(i),t()},e);return this.timers.add(i),i}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 i=0;return(...s)=>{const r=Date.now();if(r-i>=e)return i=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){}}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),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||"/externalapi/commonservice/strongPassword/getPublicKey",validateUrl:t.validateUrl||"/externalapi/commonservice/strongPassword/checkPassword",baseUrl:t.baseUrl||"",timeout:t.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:t.headers||{},cacheDuration:t.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...t},this.options.publicKeyUrl=this.normalizeUrl(this.options.publicKeyUrl,this.options.baseUrl),this.options.validateUrl=this.normalizeUrl(this.options.validateUrl,this.options.baseUrl),this.publicKeyCache=null,this.publicKeyExpiry=null}normalizeUrl(t,e){return t?/^https?:\/\//.test(t)||!e?t:(e.endsWith("/")?e:e+"/")+(t.startsWith("/")?t.substring(1):t):t}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,i=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]||e||"未知错误";this.options.onError&&this.options.onError({type:t,message:s,originalError:i})}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 i=new JSEncrypt;i.setPublicKey(e);const s=i.encrypt(t);if(!s)throw Error("密码加密失败,可能是公钥格式不正确");return s}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,e,i={}){try{if(!t||!e)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:e,password:r,timestamp:Date.now(),...i})});return{success:this.isSuccessResponse(a),data:a.data||a.result,message:a.message||a.msg||"验证完成",code:a.code||a.status,originalResponse:a}}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 i=new AbortController,s=setTimeout(()=>{i.abort()},this.options.timeout);try{const r=await fetch(t,{...e,headers:{"Content-Type":"application/json",...this.options.headers,...e.headers},signal:i.signal});if(clearTimeout(s),!r.ok)throw Error(`HTTP错误: ${r.status} ${r.statusText}`);const a=await r.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()}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){}}}a={PopupSliderCaptcha:s,PasswordValidator:r=PasswordValidator,createPasswordValidator:e,validatePassword:i},"undefined"!=typeof window&&(window.SliderCaptcha=s,window.PopupSliderCaptcha=s,window.PasswordValidator=r,window.createPasswordValidator=e,window.validatePassword=i),t.PasswordValidator=r,t.PopupSliderCaptcha=s,t.createPasswordValidator=e,t.default=a,t.validatePassword=i,Object.defineProperty(t,"t",{value:!0})});
@@ -1,342 +1 @@
1
- import JSEncrypt from 'jsencrypt';
2
-
3
- /**
4
- * 密码校验工具类
5
- * 提供密码加密和校验功能
6
- */
7
- class PasswordValidator {
8
- static CONSTANTS = {
9
- DEFAULT_TIMEOUT: 10000,
10
- CACHE_DURATION: 5 * 60 * 1000, // 5分钟缓存
11
- MIN_PASSWORD_LENGTH: 1
12
- }
13
-
14
- static ERROR_TYPES = {
15
- NETWORK_ERROR: 'NETWORK_ERROR',
16
- TIMEOUT_ERROR: 'TIMEOUT_ERROR',
17
- ENCRYPTION_ERROR: 'ENCRYPTION_ERROR',
18
- VALIDATION_ERROR: 'VALIDATION_ERROR',
19
- PUBLIC_KEY_ERROR: 'PUBLIC_KEY_ERROR'
20
- }
21
-
22
- constructor(options = {}) {
23
- this.options = {
24
- publicKeyUrl: options.publicKeyUrl || '/microservice/strongPassword/getPublicKey',
25
- validateUrl: options.validateUrl || '/microservice/strongPassword/checkPassword',
26
- timeout: options.timeout || PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,
27
- headers: options.headers || {},
28
- cacheDuration: options.cacheDuration || PasswordValidator.CONSTANTS.CACHE_DURATION,
29
- ...options
30
- };
31
-
32
- // 缓存公钥,避免重复请求
33
- this.publicKeyCache = null;
34
- this.publicKeyExpiry = null;
35
- }
36
-
37
- /**
38
- * 获取公钥
39
- * @returns {Promise<string>} 公钥字符串
40
- */
41
- async getPublicKey() {
42
- // 检查缓存是否有效
43
- if (this.publicKeyCache && this.publicKeyExpiry && Date.now() < this.publicKeyExpiry) {
44
- return this.publicKeyCache
45
- }
46
-
47
- try {
48
- const response = await this.makeRequest(this.options.publicKeyUrl, {
49
- method: 'GET'
50
- });
51
-
52
- if (!this.isSuccessResponse(response)) {
53
- throw new Error(`获取公钥失败:${response.message || response.msg || '未知错误'}`)
54
- }
55
-
56
- // 验证公钥格式
57
- const publicKey = response.data || response.result;
58
- if (!publicKey) {
59
- throw new Error('公钥为空')
60
- }
61
-
62
- // 缓存公钥
63
- this.publicKeyCache = publicKey;
64
- this.publicKeyExpiry = Date.now() + this.options.cacheDuration;
65
-
66
- return this.publicKeyCache
67
- } catch (error) {
68
- console.error('获取公钥失败:', error);
69
- this.handleError(PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR, error.message, error);
70
- throw new Error(`获取公钥失败: ${error.message}`)
71
- }
72
- }
73
-
74
- // 优化:添加响应成功判断方法
75
- isSuccessResponse(response) {
76
- if (!response || typeof response !== 'object') {
77
- return false
78
- }
79
-
80
- const successIndicators = [
81
- response.code === '0',
82
- response.code === 0,
83
- response.success === true,
84
- response.status === 'success',
85
- response.result === true
86
- ];
87
-
88
- return successIndicators.some((indicator) => indicator === true)
89
- }
90
-
91
- handleError(errorType, message, originalError = null) {
92
- const errorMessages = {
93
- [PasswordValidator.ERROR_TYPES.NETWORK_ERROR]: '网络连接失败,请检查网络设置',
94
- [PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR]: '请求超时,请重试',
95
- [PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR]: '密码加密失败',
96
- [PasswordValidator.ERROR_TYPES.VALIDATION_ERROR]: '密码验证失败',
97
- [PasswordValidator.ERROR_TYPES.PUBLIC_KEY_ERROR]: '获取公钥失败'
98
- };
99
-
100
- const errorMessage = errorMessages[errorType] || message || '未知错误';
101
-
102
- // 调用用户自定义错误处理
103
- if (this.options.onError) {
104
- this.options.onError({
105
- type: errorType,
106
- message: errorMessage,
107
- originalError
108
- });
109
- }
110
-
111
- console.error(`密码校验器错误 [${errorType}]:`, errorMessage, originalError);
112
- }
113
-
114
- /**
115
- * 使用RSA公钥加密密码
116
- * @param {string} password 原始密码
117
- * @param {string} publicKey 公钥字符串
118
- * @returns {string} 加密后的密码
119
- */
120
- encryptPassword(password, publicKey) {
121
- try {
122
- if (!password) {
123
- throw new Error('密码不能为空')
124
- }
125
-
126
- if (password.length < PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH) {
127
- throw new Error('密码长度不足')
128
- }
129
-
130
- if (!publicKey) {
131
- throw new Error('公钥不能为空')
132
- }
133
-
134
- if (typeof JSEncrypt === 'undefined') {
135
- throw new Error('JSEncrypt库未正确加载,请确保已引入JSEncrypt库')
136
- }
137
-
138
- const encrypt = new JSEncrypt();
139
- encrypt.setPublicKey(publicKey);
140
- const encrypted = encrypt.encrypt(password);
141
-
142
- if (!encrypted) {
143
- throw new Error('密码加密失败,可能是公钥格式不正确')
144
- }
145
-
146
- return encrypted
147
- } catch (error) {
148
- console.error('密码加密失败:', error);
149
- this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR, error.message, error);
150
- throw new Error(`密码加密失败: ${error.message}`)
151
- }
152
- }
153
-
154
- /**
155
- * 校验密码
156
- * @param {string} password 原始密码
157
- * @param {string} userName 用户名
158
- * @param {Object} additionalData 额外的校验数据
159
- * @returns {Promise<Object>} 校验结果
160
- */
161
- async validatePassword(password, userName, additionalData = {}) {
162
- try {
163
- if (!password || !userName) {
164
- throw new Error('用户名密码不能为空')
165
- }
166
-
167
- // 1. 获取公钥
168
- const publicKey = await this.getPublicKey();
169
-
170
- // 2. 加密密码
171
- const encryptedPassword = this.encryptPassword(password, publicKey);
172
-
173
- // 3. 调用校验接口
174
- const response = await this.makeRequest(this.options.validateUrl, {
175
- method: 'POST',
176
- body: JSON.stringify({
177
- userName,
178
- password: encryptedPassword,
179
- timestamp: Date.now(),
180
- ...additionalData
181
- })
182
- });
183
-
184
- return {
185
- success: this.isSuccessResponse(response),
186
- data: response.data || response.result,
187
- message: response.message || response.msg || '验证完成',
188
- code: response.code || response.status,
189
- originalResponse: response
190
- }
191
- } catch (error) {
192
- console.error('密码校验失败:', error);
193
-
194
- // 优化:根据错误类型返回不同的错误信息
195
- let errorType = PasswordValidator.ERROR_TYPES.VALIDATION_ERROR;
196
- if (error.message.includes('网络') || error.message.includes('fetch')) {
197
- errorType = PasswordValidator.ERROR_TYPES.NETWORK_ERROR;
198
- } else if (error.message.includes('超时') || error.message.includes('timeout')) {
199
- errorType = PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR;
200
- } else if (error.message.includes('加密')) {
201
- errorType = PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR;
202
- }
203
-
204
- this.handleError(errorType, error.message, error);
205
-
206
- return {
207
- success: false,
208
- message: error.message || '密码校验失败',
209
- code: errorType,
210
- error
211
- }
212
- }
213
- }
214
-
215
- /**
216
- * 发送HTTP请求的通用方法
217
- * @param {string} url 请求地址
218
- * @param {Object} options 请求选项
219
- * @returns {Promise<Object>} 响应结果
220
- */
221
- async makeRequest(url, options = {}) {
222
- // 优化:为每个请求创建独立的AbortController,避免多个请求互相取消
223
- const abortController = new AbortController();
224
-
225
- const timeoutId = setTimeout(() => {
226
- abortController.abort();
227
- }, this.options.timeout);
228
-
229
- try {
230
- const response = await fetch(url, {
231
- ...options,
232
- headers: {
233
- 'Content-Type': 'application/json',
234
- ...this.options.headers,
235
- ...options.headers
236
- },
237
- signal: abortController.signal
238
- });
239
-
240
- clearTimeout(timeoutId);
241
-
242
- if (!response.ok) {
243
- throw new Error(`HTTP错误: ${response.status} ${response.statusText}`)
244
- }
245
-
246
- const data = await response.json();
247
-
248
- // 优化:记录请求日志(开发环境)
249
- if (this.options.debug) {
250
- console.log('密码校验器请求:', { url, options, response: data });
251
- }
252
-
253
- return data
254
- } catch (error) {
255
- clearTimeout(timeoutId);
256
-
257
- if (error.name === 'AbortError') {
258
- this.handleError(PasswordValidator.ERROR_TYPES.TIMEOUT_ERROR, '请求超时', error);
259
- throw new Error('请求超时')
260
- }
261
-
262
- if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
263
- this.handleError(PasswordValidator.ERROR_TYPES.NETWORK_ERROR, '网络连接失败', error);
264
- throw new Error('网络连接失败')
265
- }
266
-
267
- throw error
268
- }
269
- }
270
-
271
- /**
272
- * 清除公钥缓存
273
- */
274
- clearCache() {
275
- this.publicKeyCache = null;
276
- this.publicKeyExpiry = null;
277
- }
278
-
279
- /**
280
- * 更新配置
281
- * @param {Object} newOptions 新的配置选项
282
- */
283
- updateOptions(newOptions) {
284
- this.options = { ...this.options, ...newOptions };
285
- // 清除缓存,使用新配置重新获取
286
- this.clearCache();
287
- }
288
-
289
- /**
290
- * 获取缓存状态
291
- * @returns {Object} 缓存状态信息
292
- */
293
- getCacheStatus() {
294
- return {
295
- hasCache: Boolean(this.publicKeyCache),
296
- isExpired: this.publicKeyExpiry ? Date.now() > this.publicKeyExpiry : true,
297
- expiryTime: this.publicKeyExpiry,
298
- remainingTime: this.publicKeyExpiry ? Math.max(0, this.publicKeyExpiry - Date.now()) : 0
299
- }
300
- }
301
-
302
- /**
303
- * 销毁实例,清理资源
304
- */
305
- destroy() {
306
- try {
307
- // 清除缓存
308
- this.clearCache();
309
-
310
- // 调用销毁回调
311
- if (this.options.onDestroy) {
312
- this.options.onDestroy();
313
- }
314
- } catch (error) {
315
- console.error('销毁密码校验器时出错:', error);
316
- }
317
- }
318
- }
319
-
320
- /**
321
- * 创建密码校验器实例的工厂函数
322
- * @param {Object} options 配置选项
323
- * @returns {PasswordValidator} 密码校验器实例
324
- */
325
- function createPasswordValidator(options) {
326
- return new PasswordValidator(options)
327
- }
328
-
329
- /**
330
- * 快速校验密码的便捷函数
331
- * @param {string} password 密码
332
- * @param {string} userName 用户名
333
- * @param {Object} options 配置选项
334
- * @param {Object} additionalData 额外数据
335
- * @returns {Promise<Object>} 校验结果
336
- */
337
- function validatePassword(password, userName, options = {}, additionalData = {}) {
338
- const validator = new PasswordValidator(options);
339
- return validator.validatePassword(password, userName, additionalData)
340
- }
341
-
342
- export { createPasswordValidator, PasswordValidator as default, validatePassword };
1
+ function t(t){return new PasswordValidator(t)}function r(t,r,s={},i={}){return new PasswordValidator(s).validatePassword(t,r,i)}import JSEncrypt from"jsencrypt";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||"/externalapi/commonservice/strongPassword/getPublicKey",validateUrl:t.validateUrl||"/externalapi/commonservice/strongPassword/checkPassword",baseUrl:t.baseUrl||"",timeout:t.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:t.headers||{},cacheDuration:t.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...t},this.options.publicKeyUrl=this.normalizeUrl(this.options.publicKeyUrl,this.options.baseUrl),this.options.validateUrl=this.normalizeUrl(this.options.validateUrl,this.options.baseUrl),this.publicKeyCache=null,this.publicKeyExpiry=null}normalizeUrl(t,r){return t?/^https?:\/\//.test(t)||!r?t:(r.endsWith("/")?r:r+"/")+(t.startsWith("/")?t.substring(1):t):t}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 r=t.data||t.result;if(!r)throw Error("公钥为空");return this.publicKeyCache=r,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,r,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]||r||"未知错误";this.options.onError&&this.options.onError({type:t,message:i,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(void 0===JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const s=new JSEncrypt;s.setPublicKey(r);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,r,s={}){try{if(!t||!r)throw Error("用户名密码不能为空");const i=await this.getPublicKey(),e=this.encryptPassword(t,i),o=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:r,password:e,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){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={}){const s=new AbortController,i=setTimeout(()=>{s.abort()},this.options.timeout);try{const e=await fetch(t,{...r,headers:{"Content-Type":"application/json",...this.options.headers,...r.headers},signal:s.signal});if(clearTimeout(i),!e.ok)throw Error(`HTTP错误: ${e.status} ${e.statusText}`);const o=await e.json();return this.options.debug,o}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){}}}export{t as createPasswordValidator,PasswordValidator as default,r as validatePassword};
@@ -1 +1 @@
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})});
1
+ !function(t,r){"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)}(this,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||"/externalapi/commonservice/strongPassword/getPublicKey",validateUrl:t.validateUrl||"/externalapi/commonservice/strongPassword/checkPassword",baseUrl:t.baseUrl||"",timeout:t.timeout||PasswordValidator.CONSTANTS.DEFAULT_TIMEOUT,headers:t.headers||{},cacheDuration:t.cacheDuration||PasswordValidator.CONSTANTS.CACHE_DURATION,...t},this.options.publicKeyUrl=this.normalizeUrl(this.options.publicKeyUrl,this.options.baseUrl),this.options.validateUrl=this.normalizeUrl(this.options.validateUrl,this.options.baseUrl),this.publicKeyCache=null,this.publicKeyExpiry=null}normalizeUrl(t,r){return t?/^https?:\/\//.test(t)||!r?t:(r.endsWith("/")?r:r+"/")+(t.startsWith("/")?t.substring(1):t):t}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 r=t.data||t.result;if(!r)throw Error("公钥为空");return this.publicKeyCache=r,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,r,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]||r||"未知错误";this.options.onError&&this.options.onError({type:t,message:s,originalError:e})}encryptPassword(t,r){try{if(!t)throw Error("密码不能为空");if(t.length<PasswordValidator.CONSTANTS.MIN_PASSWORD_LENGTH)throw Error("密码长度不足");if(!r)throw Error("公钥不能为空");if(void 0===JSEncrypt)throw Error("JSEncrypt库未正确加载,请确保已引入JSEncrypt库");const e=new JSEncrypt;e.setPublicKey(r);const s=e.encrypt(t);if(!s)throw Error("密码加密失败,可能是公钥格式不正确");return s}catch(t){throw this.handleError(PasswordValidator.ERROR_TYPES.ENCRYPTION_ERROR,t.message,t),Error("密码加密失败: "+t.message)}}async validatePassword(t,r,e={}){try{if(!t||!r)throw Error("用户名密码不能为空");const s=await this.getPublicKey(),i=this.encryptPassword(t,s),o=await this.makeRequest(this.options.validateUrl,{method:"POST",body:JSON.stringify({userName:r,password:i,timestamp:Date.now(),...e})});return{success:this.isSuccessResponse(o),data:o.data||o.result,message:o.message||o.msg||"验证完成",code:o.code||o.status,originalResponse:o}}catch(t){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={}){const e=new AbortController,s=setTimeout(()=>{e.abort()},this.options.timeout);try{const i=await fetch(t,{...r,headers:{"Content-Type":"application/json",...this.options.headers,...r.headers},signal:e.signal});if(clearTimeout(s),!i.ok)throw Error(`HTTP错误: ${i.status} ${i.statusText}`);const o=await i.json();return this.options.debug,o}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()}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){}}}t.createPasswordValidator=function(t){return new PasswordValidator(t)},t.default=PasswordValidator,t.validatePassword=function(t,r,e={},s={}){return new PasswordValidator(e).validatePassword(t,r,s)},Object.defineProperty(t,"t",{value:!0})});