1) Configuration looks fine to me. But your solution won't work when there are multiple parallel requests and all of them trying to refresh auth token at the same time. Believe me this is a issue is really hard to pin point. So better be covered upfront.
2) No. Not the right place. Create a separate service (I call it api.service) and do all the network/api commutation using that.
3) There is no use of interceptor variable. You can avoid assigning it to a variable.
4) If have control over the API you can reduce the timeout for a bit. Also i think 24 hours is bit too long. Else no option I guess.
5) Not sure you have to deal with them.
Bellow is a working code of api.service.ts. You might have to change few things here and there to fit that in to your application. If you get the concept clearly it wont be hard. Also it cover multiple parallel request problem as well.
import * as queryString from 'query-string';
import axios, { AxiosRequestConfig, Method } from 'axios';
import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values
var instance = axios.create({
baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';
export const apiService = {
get,
post,
put,
patch,
delete: deleteRecord,
delete2: deleteRecord2
}
function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}
function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}
function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}
function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}
function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}
function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
var url = createUrl(controller, action, urlParams, queryParams);
var options = createRequestOptions(url, method, data);
return instance.request<T>(options)
.then(res => res && res.data)
.catch(error => {
if (error.response) {
//handle error appropriately: if you want to display a descriptive error notification this is the place
} else {
//handle error appropriately: if you want to display a a generic error message
}
throw error;
});
}
function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
let url = controller + (action ? '/' + action : '');
urlParams.forEach(param => {
url += '/' + param;
});
let params = '';
if (queryParams) {
params += '?' + queryString.stringify(queryParams);
}
return url += params;
}
function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
var authToken = storageService.getAuthToken();
var jwtToken = authToken != null ? authToken.authToken : '';
var options: AxiosRequestConfig = {
url,
method,
data,
headers: {
'Authorization': 'bearer ' + jwtToken
},
}
if (responseType) {
options.responseType = responseType;
}
return options;
}
let isRefreshing = false;
let failedQueue: any[] = [];
const processQueue = (error: any, token: string = '') => {
failedQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
}
instance.interceptors.response.use(undefined, (error) => {
const originalRequest = error.config;
if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
return new Promise(function (resolve, reject) {
failedQueue.push({ resolve, reject })
}).then(authToken => {
originalRequest.headers.Authorization = 'bearer ' + authToken;
return axios(originalRequest);
}).catch(err => {
return err;
})
}
originalRequest._retry = true;
isRefreshing = true;
return new Promise(function (resolve, reject) {
accountService.refreshToken()
.then(result => {
if (result.succeeded) {
originalRequest.headers.Authorization = 'bearer ' + result.authToken;
axios(originalRequest).then(resolve, reject);
processQueue(null, result.authToken);
} else {
reject(error);
}
}).catch((err) => {
processQueue(err);
reject(err);
}).then(() => { isRefreshing = false });
});
}
return Promise.reject(error);
});
Cheers,