axios-retryer
Version:
axios-retryer is an advanced Axios request manager offering intelligent retry logic with token refresh, concurrency control, priority queuing, and a flexible plugin architecture, all built with TypeScript for robust HTTP client integrations.
2 lines (1 loc) • 4.38 kB
JavaScript
"use strict";var e=require("axios");const r={maxRefreshAttempts:3,authHeaderName:"Authorization",refreshStatusCodes:[401],refreshTimeout:15e3,retryOnRefreshFail:!0,tokenPrefix:"Bearer "};class s{constructor(e,s){this.name="TokenRefreshPlugin",this.version="1.0.0",this.interceptorId=null,this.responseInterceptorId=null,this.isRefreshing=!1,this.refreshQueue=[],this.logger=null,this.refreshToken=e,this.options={...r,...s}}initialize(r){this.manager=r,this.refreshAxios=e.create(r.axiosInstance.defaults),this.logger=r.getLogger(),this.interceptorId=r.axiosInstance.interceptors.response.use((e=>e),(e=>this.handleResponseError(e))),this.options.customErrorDetector&&(this.responseInterceptorId=r.axiosInstance.interceptors.response.use((e=>this.handleSuccessResponse(e)),(e=>Promise.reject(e)))),this.manager.axiosInstance.interceptors.request.use((e=>{const{authHeaderName:r}=this.options;return this.manager.axiosInstance.defaults.headers.common[r]&&e.headers[r]&&(e.headers[r]=this.manager.axiosInstance.defaults.headers.common[r]),e}))}onBeforeDestroyed(e){null!=this.interceptorId&&e.axiosInstance.interceptors.response.eject(this.interceptorId),null!=this.responseInterceptorId&&e.axiosInstance.interceptors.response.eject(this.responseInterceptorId)}async handleSuccessResponse(e){var r;const{customErrorDetector:s}=this.options;if(e.config.__isRetryRefreshRequest||!s)return e;if(s(e.data)){if(null===(r=this.logger)||void 0===r||r.debug(`[${this.name}] Custom auth error detected in response body`),this.isRefreshing)return new Promise(((r,s)=>{this.refreshQueue.push({resolve:t=>{this.retryRequest(e.config,t).then(r).catch(s)},reject:s})}));try{this.isRefreshing=!0;const r=await this.executeTokenRefresh();return this.updateAuthHeader(r),this.retryQueuedRequests(r),this.retryRequest(e.config,r)}catch(e){throw this.handleRefreshFailure(),e}finally{this.isRefreshing=!1}}return e}async handleResponseError(e){const r=e.config;return r?r.__isRetryRefreshRequest?Promise.reject(e):this.isRefreshableError(e)?this.isRefreshing?this.queueRefreshRequest(r):this.handleTokenRefresh(r):Promise.reject(e):Promise.reject(e)}isRefreshableError(e){var r,s;const t=null!==(s=null===(r=e.response)||void 0===r?void 0:r.status)&&void 0!==s?s:-1;return this.options.refreshStatusCodes.includes(t)}async handleTokenRefresh(e){this.isRefreshing=!0,e.__isRetryRefreshRequest||this.manager.triggerAndEmit("onBeforeTokenRefresh");try{const r=await this.executeTokenRefresh();return this.updateAuthHeader(r),this.retryQueuedRequests(r),this.retryRequest(e,r)}catch(e){return this.handleRefreshFailure(),Promise.reject(e)}finally{this.isRefreshing=!1}}async executeTokenRefresh(){var e,r,s;if(!this.refreshToken)throw Error("No token refresh handler provided");const{maxRefreshAttempts:t,refreshTimeout:i,retryOnRefreshFail:n}=this.options;let o;for(let h=1;t>=h;h++){null===(e=this.logger)||void 0===e||e.debug(`[${this.name}] Refresh attempt ${h}/${t}`);try{const e=new Promise(((e,r)=>{const s=setTimeout((()=>r(Error("Token refresh timeout"))),i);this.refreshToken(this.refreshAxios).then((r=>{clearTimeout(s),e(r)})).catch((e=>{clearTimeout(s),r(e)}))})),{token:s}=await e;return this.manager.triggerAndEmit("onTokenRefreshed",s),null===(r=this.logger)||void 0===r||r.debug(`[${this.name}] Token successfully refreshed`),s}catch(e){if(o=e,!n)break;if(t+1>h){null===(s=this.logger)||void 0===s||s.debug(`[${this.name}] Refresh attempt failed, retrying...`);continue}break}}throw o}updateAuthHeader(e){const{authHeaderName:r,tokenPrefix:s}=this.options;this.manager.axiosInstance.defaults.headers.common[r]=`${s}${e}`}retryRequest(e,r){const{authHeaderName:s,tokenPrefix:t}=this.options;return e.__isRetryRefreshRequest=!0,e.headers={...e.headers,[s]:`${t}${r}`},this.refreshAxios(e)}queueRefreshRequest(e){return new Promise(((r,s)=>{this.refreshQueue.push({resolve:s=>r(this.retryRequest(e,s)),reject:s})}))}retryQueuedRequests(e){this.refreshQueue.forEach((({resolve:r})=>r(e))),this.refreshQueue=[]}handleRefreshFailure(){var e;const r=Error("Token refresh failed");this.refreshQueue.forEach((({reject:e})=>e(r))),this.refreshQueue=[],this.manager.triggerAndEmit("onTokenRefreshFailed"),null===(e=this.logger)||void 0===e||e.error(this.name+" Token refresh failed - clearing queue")}}exports.TokenRefreshPlugin=s,exports.createTokenRefreshPlugin=function(e,r){return new s(e,r)};