UNPKG

@alapandas03/token-refresher

Version:

A service for handling API requests with token refresh capabilities

155 lines (127 loc) 4.86 kB
const axios = require('axios'); class TokenRefresher { constructor(config = {}) { this.isRefreshing = false; this.failedQueue = []; if (!config.baseURL) { throw new Error('baseURL is required'); } this.baseURL = config.baseURL || ''; if (!config.refreshTokenEndpoint) { throw new Error('refreshTokenEndpoint is required'); } this.refreshTokenEndpoint = config.refreshTokenEndpoint; // Initialize token management functions this._getAccessToken = config.getAccessToken || this._defaultGetAccessToken; this._getRefreshToken = config.getRefreshToken || this._defaultGetRefreshToken; this._setAccessToken = config.setAccessToken || this._defaultSetAccessToken; this._clearTokens = config.clearTokens || this._defaultClearTokens; this.api = axios.create({ baseURL: this.baseURL, ...config }); this.setupInterceptors(); } // Default implementations _defaultGetAccessToken() { return typeof window !== 'undefined' ? localStorage.getItem('accessToken') : null; } _defaultGetRefreshToken() { return typeof window !== 'undefined' ? localStorage.getItem('refreshToken') : null; } _defaultSetAccessToken(token) { if (typeof window !== 'undefined') { localStorage.setItem('accessToken', token); } } _defaultClearTokens() { if (typeof window !== 'undefined') { localStorage.clear(); } } // Public token management methods getAccessToken() { return this._getAccessToken(); } getRefreshToken() { return this._getRefreshToken(); } setAccessToken(token) { return this._setAccessToken(token); } clearTokens() { return this._clearTokens(); } processQueue(error, token = null) { this.failedQueue.forEach(prom => { if (error) { prom.reject(error); } else { prom.resolve(token); } }); this.failedQueue = []; } setupInterceptors() { this.api.interceptors.request.use( config => { const token = this.getAccessToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => Promise.reject(error) ); this.api.interceptors.response.use( response => response, async error => { const originalRequest = error.config; if (error.response?.status !== 401 || originalRequest._retry) { return Promise.reject(error); } if (this.isRefreshing) { return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject }); }) .then(token => { originalRequest.headers.Authorization = `Bearer ${token}`; return this.api(originalRequest); }) .catch(err => Promise.reject(err)); } originalRequest._retry = true; this.isRefreshing = true; try { const refreshToken = this.getRefreshToken(); const response = await this.api.post(this.refreshTokenEndpoint, { refreshToken }); const { accessToken } = response.data; this.setAccessToken(accessToken); originalRequest.headers.Authorization = `Bearer ${accessToken}`; this.processQueue(null, accessToken); return this.api(originalRequest); } catch (refreshError) { this.processQueue(refreshError, null); this.clearTokens(); return Promise.reject(refreshError); } finally { this.isRefreshing = false; } } ); } // Public API methods async get(url, config = {}) { return this.api.get(url, config); } async post(url, data, config = {}) { return this.api.post(url, data, config); } async put(url, data, config = {}) { return this.api.put(url, data, config); } async delete(url, config = {}) { return this.api.delete(url, config); } } module.exports = TokenRefresher;