I am currently using the following api client written in Typescript:
enum StatusCode {
Unauthorized = 401,
Forbidden = 403,
TooManyRequests = 429,
InternalServerError = 500,
}
const headers: Readonly<Record<string, string | boolean>> = {
Accept: "application/json",
"Content-Type": "application/json",
};
const injectToken = (config: AxiosRequestConfig): AxiosRequestConfig => {
if (
config.baseURL === API_HOST &&
!config.headers.Authorization &&
useAuthStatus.getState().status === "logged-in"
) {
try {
const token = getAccessToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
} catch (error) {
throw new Error(error);
}
}
};
class Http {
private instance: AxiosInstance | null = null;
private get http(): AxiosInstance {
return this.instance != null ? this.instance : this.initHttp();
}
initHttp() {
const http = axios.create({
baseURL: API_HOST,
headers,
});
http.interceptors.request.use(injectToken, (error) => {
Promise.reject(error);
});
http.interceptors.response.use(
(response) => {
const { data } = response;
return data;
},
(error) => {
const { response } = error;
return this.handleError(response);
}
);
this.instance = http;
return http;
}
request<T = any, R = AxiosResponse<T>>(
config: AxiosRequestConfig
): Promise<R> {
return this.http.request(config);
}
get<T = any, R = AxiosResponse<T>>(
url: string,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.get<T, R>(url, config);
}
post<T = any, R = AxiosResponse<T>>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.post<T, R>(url, data, config);
}
put<T = any, R = AxiosResponse<T>>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.put<T, R>(url, data, config);
}
patch<T = any, R = AxiosResponse<T>>(
url: string,
data?: T,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.patch<T, R>(url, data, config);
}
delete<T = any, R = AxiosResponse<T>>(
url: string,
config?: AxiosRequestConfig
): Promise<R> {
return this.http.delete<T, R>(url, config);
}
private handleError(error) {
const { status } = error;
switch (status) {
case StatusCode.InternalServerError: {
break;
}
case StatusCode.Forbidden: {
// Handle Forbidden
break;
}
case StatusCode.Unauthorized: {
// Handle Unauthorized
if (useAuthStatus.getState().status === "logged-in") {
return refreshToken().then((_) => {
error.config.headers.Authorization = `Bearer ${
useTokenStore.getState().accessToken
}`;
return this.request(error.config);
});
}
return Promise.reject(error);
}
case StatusCode.TooManyRequests: {
// Handle TooManyRequests
break;
}
}
return Promise.reject(error);
}
}
export const http = new Http();
currently I have the problem that I can't manage to repeat the request after a successful token refresh in case of a 401 error. The problem is that I call ``this.request(error.config)```. I get the following error, which I can't explain:
TypeError: Cannot read property 'status' of undefined
at Http.handleError (http.ts?5220:118)
at eval (http.ts?5220:62)
If I replace this.request(error.config) with axios.request(error.config), the retry works fine, but my interceptors are no longer set as it creates a new axios instance. I would like to continue the retry with the same Axios instance (http in my case).
I just see that I fall into the catch block on this interceptor and the error is as follows:
http.interceptors.response.use(
(response) => {
const { data } = response;
return data;
},
(error) => {
const { response } = error;
return this.handleError(response);
}
)
results in:
TypeError: Cannot read property 'cancelToken' of undefined
What could be the reason for this behavior?