@alapandas03/token-refresher
Version:
A service for handling API requests with token refresh capabilities
155 lines (127 loc) • 4.86 kB
JavaScript
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;