-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathaxiosInterceptors.ts
153 lines (136 loc) · 5.35 KB
/
axiosInterceptors.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import axios, { AxiosRequestConfig, CancelToken, AxiosError, AxiosResponse } from 'axios';
import { isDebugEnabled } from './debug';
import {
fetchCachedResponse,
saveCacheResponse,
CacheConfig,
removeCacheableRequestsInProgress,
deleteExpiredCachedItemsAtInterval,
} from './cacheHandlers';
const DEFAULT_RETRY_DELAY = 3000;
const DEFAULT_MAX_RETRIES = 2;
// we are extending axios' AxiosRequestConfig:
// https://stackoverflow.com/questions/58777924/axios-typescript-customize-axiosrequestconfig
declare module 'axios' {
export interface AxiosRequestConfig {
retries?: number;
cancelToken?: CancelToken;
cacheKey?: string;
cache?: CacheConfig;
rewriteUrlFunc?: (url: string) => string;
setCancelTokenCacheKey?: (cacheKey: string) => void;
}
}
export const registerInitialAxiosInterceptors = (): any => {
deleteExpiredCachedItemsAtInterval();
// - the interceptors are called in reverse order in which they are registered - last
// defined interceptor is called first
// - some interceptors might also be added in other places (`registerHostnameReplacing()`)
axios.interceptors.request.use(logCurl, (error) => Promise.reject(error));
axios.interceptors.request.use(fetchCachedResponse, (error) => Promise.reject(error));
axios.interceptors.request.use(rewriteUrl, (error) => Promise.reject(error));
axios.interceptors.response.use(saveCacheResponse, (error) => retryRequests(error));
};
const rewriteUrl = async (config: any): Promise<any> => {
if (config.rewriteUrlFunc) {
config.url = config.rewriteUrlFunc(config.url);
}
return config;
};
const logCurl = async (config: any): Promise<any> => {
if (isDebugEnabled()) {
// Headers are not represented in a very straighforward way in axios, so we must transform
// them. This is the contents of axios' config.headers:
// {
// common: { Accept: 'application/json, text/plain, */*' },
// delete: {},
// get: {},
// head: {},
// post: { 'Content-Type': 'application/x-www-form-urlencoded' },
// put: { 'Content-Type': 'application/x-www-form-urlencoded' },
// patch: { 'Content-Type': 'application/x-www-form-urlencoded' },
// Authorization: 'Bearer eyJra...'
// },
let headers = {
...config.headers.common,
...config.headers[config.method],
};
const addedHeadersKeys = Object.keys(config.headers).filter((k) => typeof config.headers[k] === 'string');
addedHeadersKeys.forEach((k) => (headers[k] = config.headers[k]));
// findDatesUTC on S1GRDAWSEULayer doesn't specify JSON Content-Type, but the request still works as if it was specified. On
// the other hand, when requesting auth token, we use Content-Type 'application/x-www-form-urlencoded'. This hack updates a
// Content-Type header to JSON whenever data is not a string:
if (typeof config.data !== 'string') {
headers['Content-Type'] = 'application/json';
}
// we sometimes get both 'Content-Type' and 'content-type', making /oauth/token/ endpoint complain
let lowercaseHeaders: Record<string, string> = {};
for (let k in headers) {
lowercaseHeaders[k.toLowerCase()] = headers[k];
}
console.debug(
`${'*'.repeat(30)}\n${curlify(
config.url,
config.method.toUpperCase(),
config.data,
lowercaseHeaders,
)}\n\n`,
);
}
return config;
};
function curlify(
url: string,
method: string,
payload: any | null = null,
headers: Record<string, string> = {},
): string {
let curl = `curl -X ${method} '${url}'`;
for (let h in headers) {
curl += ` -H '${h}: ${headers[h]}'`;
}
if (payload) {
curl += ` -d '${typeof payload === 'string' ? payload : JSON.stringify(payload)}'`;
}
return curl;
}
const retryRequests = (err: any): any => {
if (!err.config) {
return Promise.reject(err);
}
if (err.config.cacheKey) {
removeCacheableRequestsInProgress(err.config.cacheKey);
}
if (shouldRetry(err)) {
err.config.retriesCount = err.config.retriesCount | 0;
const maxRetries =
err.config.retries === undefined || err.config.retries === null
? DEFAULT_MAX_RETRIES
: err.config.retries;
const shouldRetry = err.config.retriesCount < maxRetries;
if (shouldRetry) {
err.config.retriesCount += 1;
err.config.transformRequest = [(data: any) => data];
return new Promise((resolve) => setTimeout(() => resolve(axios(err.config)), DEFAULT_RETRY_DELAY));
}
}
return Promise.reject(err);
};
const shouldRetry = (error: AxiosError): boolean => {
// error.response is not always defined, as the error could be thrown before we get a response from the server
// https://github.com/axios/axios/issues/960#issuecomment-398269712
if (!error.response || !error.response.status) {
return false;
}
return error.response.status == 429 || (error.response.status >= 500 && error.response.status <= 599);
};
export const addAxiosRequestInterceptor = (
customInterceptor: (config: AxiosRequestConfig) => AxiosRequestConfig | Promise<AxiosRequestConfig>,
): void => {
axios.interceptors.request.use(customInterceptor, (error) => Promise.reject(error));
};
export const addAxiosResponseInterceptor = (
customInterceptor: (config: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>,
): void => {
axios.interceptors.response.use(customInterceptor, (error) => Promise.reject(error));
};