-
Star
(231)
You must be signed in to star a gist -
Fork
(42)
You must be signed in to fork a gist
-
-
Save mkjiau/650013a99c341c9f23ca00ccb213db1c to your computer and use it in GitHub Desktop.
let isRefreshing = false; | |
let refreshSubscribers = []; | |
const instance = axios.create({ | |
baseURL: Config.API_URL, | |
}); | |
instance.interceptors.response.use(response => { | |
return response; | |
}, error => { | |
const { config, response: { status } } = error; | |
const originalRequest = config; | |
if (status === 498) { | |
if (!isRefreshing) { | |
isRefreshing = true; | |
refreshAccessToken() | |
.then(newToken => { | |
isRefreshing = false; | |
onRrefreshed(newToken); | |
}); | |
} | |
const retryOrigReq = new Promise((resolve, reject) => { | |
subscribeTokenRefresh(token => { | |
// replace the expired token and retry | |
originalRequest.headers['Authorization'] = 'Bearer ' + token; | |
resolve(axios(originalRequest)); | |
}); | |
}); | |
return retryOrigReq; | |
} else { | |
return Promise.reject(error); | |
} | |
}); | |
subscribeTokenRefresh(cb) { | |
refreshSubscribers.push(cb); | |
} | |
onRrefreshed(token) { | |
refreshSubscribers.map(cb => cb(token)); | |
} |
Can someone explain how to show message toast just once if for example we get from several requests 401 status at the same time ?
How to write correctly in interceptor it ? - with toggle it doesn't work - because it always jump to check by 401 status and change toggle every bad request that you get
const responseInterceptor = axiosAPI.interceptors.response.use(
response => response,
async (error) => {
const {config: originalRequest, response: {status}} = error
if (status === 401 || status === 403) {
if (!isFetchingToken) {
isFetchingToken = true
returnMessage(status).then(() => {
isFetchingToken = false
})
}
return Promise.reject(error)
}
)
I just want to one error message - and it doesn't matter how many requests I use
I would be grateful if someone could advise)
here is my axios instance file, all requests perform sequentially and if i get one 401 response, all other go to queue and relieve only after token refresh
so i don't get more then one 401
import axios, { AxiosError } from 'axios';
import { Store } from 'redux';
import axiosMiddlewareFactory from 'redux-axios-middleware';
import AppConfig from '~/config/appConfig';
import needError from '~/helpers/needError';
import { networkOffline, showError } from '~/modules/app/actions/AppActions';
import {
IRefreshTokenData,
logout,
refreshToken,
refreshTokenSuccess,
} from '~/modules/auth/actions/AuthActions';
import {
getIsAuthenticated,
getRefreshToken,
getToken,
} from '~/modules/auth/AuthSelectors';
import { ErrorCodes } from '~/modules/auth/models';
import { getProfile } from '~/modules/settings/SettingsSelector';
type IRequestCb = (token: string) => void;
export const axiosClient = axios.create({
baseURL: AppConfig.apiUrl,
responseType: 'json',
});
let isRefreshing = false;
let refreshSubscribers: IRequestCb[] = [];
let refreshRetry = true;
const subscribeTokenRefresh = (cb: IRequestCb) => {
refreshSubscribers.push(cb);
};
const onRefreshed = (token: string) => {
refreshSubscribers.map(cb => cb(token));
};
const axiosMiddlewareOptions = {
interceptors: {
request: [
({ getState }: Store, request: any) => {
const state = getState();
const token = getToken(state);
if (token) {
request.headers.authorization = `Bearer ${token}`;
}
return request;
},
],
response: [
{
error: function ({ getState, dispatch }: Store, error: AxiosError) {
const state = getState();
const accessToken = getToken(state);
const profile = getProfile(state);
const { teamId, roleId } = profile || {};
const isAuthenticated = getIsAuthenticated(state);
if (error?.response?.status === 401) {
const refresh = getRefreshToken(state);
const originalRequest = error.config;
const retryOrigReq = new Promise(resolve => {
subscribeTokenRefresh((newToken: string) => {
// replace the expired token and retry
if (originalRequest.headers) {
originalRequest.headers.authorization = `Bearer ${newToken}`;
}
resolve(axios(originalRequest));
});
});
if (!isRefreshing && accessToken && refresh && roleId && teamId) {
isRefreshing = true;
refreshToken({
accessToken,
refreshToken: refresh,
roleId,
teamId,
})
.then(
({
accessToken: newAccessToken,
refreshToken: newRefreshToken,
}: IRefreshTokenData) => {
if (originalRequest.headers) {
originalRequest.headers.authorization = `Bearer ${newAccessToken}`;
dispatch(
refreshTokenSuccess({
accessToken: newAccessToken,
refreshToken: newRefreshToken,
roleId,
teamId,
}),
);
refreshRetry = true;
onRefreshed(newAccessToken);
}
},
)
.catch(e => {
Bugsnag.notify(e);
if (
e.response?.data?.error &&
needError(e.response?.config)
) {
dispatch(showError(e.response?.data.error));
} else if (refreshRetry) {
refreshRetry = false;
} else {
dispatch(
showError(
'Unable to restore session. Please login again',
),
);
dispatch(logout());
}
return Promise.reject(error);
})
.finally(() => {
isRefreshing = false;
});
}
return retryOrigReq;
} else if (error?.response?.status === 403) {
// user deactivated
dispatch(
showError(
'Your account has been locked. Contact your support person to unlock it, then try again.',
),
);
dispatch(logout());
} else if (
[
ErrorCodes.passwordExpired,
ErrorCodes.accountDeleted,
ErrorCodes.accountLocked,
].includes(error?.code as ErrorCodes)
) {
if (isAuthenticated) {
// password expired, account deleted, locked
dispatch(showError(error.message));
dispatch(logout());
}
} else {
if (error.code === ErrorCodes.network) {
dispatch(networkOffline());
} else if (
((error.response?.data as { error: string })?.error ||
error.message) &&
needError(error.response?.config)
) {
dispatch(
showError(
(error.response?.data as { error: string })?.error ||
error.message,
),
);
}
return Promise.reject(error);
}
},
},
],
},
};
const axiosMiddleware = axiosMiddlewareFactory(
axiosClient,
axiosMiddlewareOptions,
);
export default axiosMiddleware;
FishManHell
here is my axios instance file, all requests perform sequentially and if i get one 401 response, all other go to queue and relieve only after token refresh so i don't get more then one 401
import axios, { AxiosError } from 'axios'; import { Store } from 'redux'; import axiosMiddlewareFactory from 'redux-axios-middleware'; import AppConfig from '~/config/appConfig'; import needError from '~/helpers/needError'; import { networkOffline, showError } from '~/modules/app/actions/AppActions'; import { IRefreshTokenData, logout, refreshToken, refreshTokenSuccess, } from '~/modules/auth/actions/AuthActions'; import { getIsAuthenticated, getRefreshToken, getToken, } from '~/modules/auth/AuthSelectors'; import { ErrorCodes } from '~/modules/auth/models'; import { getProfile } from '~/modules/settings/SettingsSelector'; type IRequestCb = (token: string) => void; export const axiosClient = axios.create({ baseURL: AppConfig.apiUrl, responseType: 'json', }); let isRefreshing = false; let refreshSubscribers: IRequestCb[] = []; let refreshRetry = true; const subscribeTokenRefresh = (cb: IRequestCb) => { refreshSubscribers.push(cb); }; const onRefreshed = (token: string) => { refreshSubscribers.map(cb => cb(token)); }; const axiosMiddlewareOptions = { interceptors: { request: [ ({ getState }: Store, request: any) => { const state = getState(); const token = getToken(state); if (token) { request.headers.authorization = `Bearer ${token}`; } return request; }, ], response: [ { error: function ({ getState, dispatch }: Store, error: AxiosError) { const state = getState(); const accessToken = getToken(state); const profile = getProfile(state); const { teamId, roleId } = profile || {}; const isAuthenticated = getIsAuthenticated(state); if (error?.response?.status === 401) { const refresh = getRefreshToken(state); const originalRequest = error.config; const retryOrigReq = new Promise(resolve => { subscribeTokenRefresh((newToken: string) => { // replace the expired token and retry if (originalRequest.headers) { originalRequest.headers.authorization = `Bearer ${newToken}`; } resolve(axios(originalRequest)); }); }); if (!isRefreshing && accessToken && refresh && roleId && teamId) { isRefreshing = true; refreshToken({ accessToken, refreshToken: refresh, roleId, teamId, }) .then( ({ accessToken: newAccessToken, refreshToken: newRefreshToken, }: IRefreshTokenData) => { if (originalRequest.headers) { originalRequest.headers.authorization = `Bearer ${newAccessToken}`; dispatch( refreshTokenSuccess({ accessToken: newAccessToken, refreshToken: newRefreshToken, roleId, teamId, }), ); refreshRetry = true; onRefreshed(newAccessToken); } }, ) .catch(e => { Bugsnag.notify(e); if ( e.response?.data?.error && needError(e.response?.config) ) { dispatch(showError(e.response?.data.error)); } else if (refreshRetry) { refreshRetry = false; } else { dispatch( showError( 'Unable to restore session. Please login again', ), ); dispatch(logout()); } return Promise.reject(error); }) .finally(() => { isRefreshing = false; }); } return retryOrigReq; } else if (error?.response?.status === 403) { // user deactivated dispatch( showError( 'Your account has been locked. Contact your support person to unlock it, then try again.', ), ); dispatch(logout()); } else if ( [ ErrorCodes.passwordExpired, ErrorCodes.accountDeleted, ErrorCodes.accountLocked, ].includes(error?.code as ErrorCodes) ) { if (isAuthenticated) { // password expired, account deleted, locked dispatch(showError(error.message)); dispatch(logout()); } } else { if (error.code === ErrorCodes.network) { dispatch(networkOffline()); } else if ( ((error.response?.data as { error: string })?.error || error.message) && needError(error.response?.config) ) { dispatch( showError( (error.response?.data as { error: string })?.error || error.message, ), ); } return Promise.reject(error); } }, }, ], }, }; const axiosMiddleware = axiosMiddlewareFactory( axiosClient, axiosMiddlewareOptions, ); export default axiosMiddleware;
FishManHell
Hi, I hope you're doing well.
can you please show an example that how to make a request?
could I just use axios client?
thanks
you need to clear array after callbacks
onRrefreshed(token) {
refreshSubscribers.map(cb => cb(token));
refreshSubscribers = [];
}
thanks, that's true