-
Notifications
You must be signed in to change notification settings - Fork 15
/
RESTClient.ts
106 lines (88 loc) · 3.21 KB
/
RESTClient.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
import axios, {AxiosError, AxiosInstance, AxiosInterceptorManager, AxiosRequestConfig, AxiosResponse} from 'axios';
import {LoginAPI} from '../login';
import {MarketAPI} from '../market';
import {DealingAPI} from '../dealing';
import {AccountAPI} from '../account';
import axiosRetry from 'axios-retry';
export interface Authorization {
accessToken?: string;
accountId?: string;
clientSessionToken?: string;
lightstreamerEndpoint?: string;
refreshToken?: string;
securityToken?: string;
username?: string;
}
export class RESTClient {
get defaults(): AxiosRequestConfig {
return this.httpClient.defaults;
}
get interceptors(): {
request: AxiosInterceptorManager<AxiosRequestConfig>;
response: AxiosInterceptorManager<AxiosResponse>;
} {
return this.httpClient.interceptors;
}
readonly login: LoginAPI;
readonly market: MarketAPI;
readonly dealing: DealingAPI;
readonly account: AccountAPI;
readonly httpClient: AxiosInstance;
readonly auth: Authorization = {};
constructor(baseURL: string, private readonly apiKey: string) {
this.httpClient = axios.create({
baseURL: baseURL,
});
function randomNum(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1) + min);
}
axiosRetry(this.httpClient, {
retries: Infinity,
retryCondition: (error: AxiosError) => {
const errorCode = error.response?.data?.errorCode;
const gotRateLimited = errorCode === 'error.public-api.exceeded-api-key-allowance';
const expiredSecurityToken = errorCode === 'error.security.oauth-token-invalid';
const missingToken = errorCode === 'error.security.client-token-missing';
if (gotRateLimited) {
return true;
} else if (expiredSecurityToken || missingToken) {
void this.login.refreshToken();
return true;
}
return true;
},
retryDelay: (retryCount: number) => {
/** Rate limits: https://labs.ig.com/faq */
return randomNum(1000, 3000) * retryCount;
},
});
this.httpClient.interceptors.request.use(async config => {
const updatedHeaders = {
...config.headers,
'X-IG-API-KEY': this.apiKey,
};
const {accessToken, accountId, securityToken, clientSessionToken} = this.auth;
if (config.url === LoginAPI.URL.SESSION && config.method === 'put') {
// Edge case to switch accounts which doesn't work with Bearer tokens
updatedHeaders['X-SECURITY-TOKEN'] = securityToken;
updatedHeaders.CST = clientSessionToken;
} else {
if (accessToken) {
updatedHeaders.Authorization = 'Bearer ' + accessToken;
} else if (securityToken && clientSessionToken) {
updatedHeaders['X-SECURITY-TOKEN'] = securityToken;
updatedHeaders.CST = clientSessionToken;
}
if (accountId) {
updatedHeaders['IG-ACCOUNT-ID'] = accountId;
}
}
config.headers = updatedHeaders;
return config;
});
this.login = new LoginAPI(this.httpClient, this.auth);
this.market = new MarketAPI(this.httpClient);
this.dealing = new DealingAPI(this.httpClient);
this.account = new AccountAPI(this.httpClient);
}
}