Skip to content

Commit b3e2a50

Browse files
authored
Merge pull request #10 from Artlfmj/master
adds ratelimiting to stats endpoints
2 parents 9e0bb19 + 67bb9a1 commit b3e2a50

File tree

6 files changed

+79
-14
lines changed

6 files changed

+79
-14
lines changed

package-lock.json

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
},
4949
"dependencies": {
5050
"axios": "^0.27.2",
51+
"axios-rate-limit": "^1.3.0",
5152
"tslib": "^2.4.0"
5253
}
5354
}

resources/structs.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,19 @@ export interface ClientConfig {
1111
apiKey?: string;
1212

1313
/**
14-
* The default language for all endpoints. Defaults to 'en'
14+
* The default language for all endpoints. Defaults to `en`
1515
*/
1616
language: Language;
17+
18+
/**
19+
* Extra timeout for stats ratelimits. Defaults to `0`.
20+
*
21+
* Normally the client will send 3 stats requests per 1100 milliseconds.
22+
* Setting this option to `100` increases the rate to 3 per 1200 milliseconds.
23+
*
24+
* You should increase this option if you're getting 429 responses
25+
*/
26+
rateLimitExtraTimeout: number;
1727
}
1828

1929
export interface ClientOptions extends Partial<ClientConfig> {}

src/client/Client.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Client {
2121
constructor(config?: ClientOptions) {
2222
this.config = {
2323
language: Language.English,
24+
rateLimitExtraTimeout: 0,
2425
...config,
2526
};
2627

@@ -186,7 +187,7 @@ class Client {
186187
* @param options Options for this endpoint
187188
*/
188189
public async brStats(options: BRStatsRequestParams): Promise<BRStatsResponseData> {
189-
return this.http.fetch('/v2/stats/br/v2', options);
190+
return this.http.fetchStats('/v2/stats/br/v2', options);
190191
}
191192

192193
/**
@@ -195,7 +196,7 @@ class Client {
195196
* @param options Options for this endpoint
196197
*/
197198
public async brStatsByID(options: { id: string } & BRStatsByAccountIDRequestParams): Promise<BRStatsByAccountIDResponseData> {
198-
return this.http.fetch(`/v2/stats/br/v2/${options.id}`, options);
199+
return this.http.fetchStats(`/v2/stats/br/v2/${options.id}`, options);
199200
}
200201
}
201202

src/http/HTTP.ts

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
/* eslint-disable no-restricted-syntax */
22
import axios, { AxiosError, AxiosInstance } from 'axios';
3-
import { URLSearchParams } from 'url';
3+
import rateLimit, { RateLimitedAxiosInstance } from 'axios-rate-limit';
44
import { version } from '../../package.json';
55
import Client from '../client/Client';
66
import FortniteAPIError from '../exceptions/FortniteAPIError';
77
import InvalidAPIKeyError from '../exceptions/InvalidAPIKeyError';
88
import MissingAPIKeyError from '../exceptions/MissingAPIKeyError';
9+
import { serializeParams } from '../util/util';
910
import { FortniteAPIResponseData } from './httpStructs';
1011

1112
class HTTP {
1213
public client: Client;
1314
public axios: AxiosInstance;
15+
public statsAxios: RateLimitedAxiosInstance;
1416
constructor(client: Client) {
1517
this.client = client;
1618

@@ -26,26 +28,45 @@ class HTTP {
2628
} : {},
2729
},
2830
});
31+
32+
this.statsAxios = rateLimit(this.axios, {
33+
maxRequests: 3,
34+
perMilliseconds: 1100 + this.client.config.rateLimitExtraTimeout,
35+
});
2936
}
3037

3138
public async fetch(url: string, params?: any): Promise<FortniteAPIResponseData> {
3239
try {
3340
const response = await this.axios({
3441
url,
3542
params,
36-
paramsSerializer: (p) => {
37-
const searchParams = new URLSearchParams();
43+
paramsSerializer: serializeParams,
44+
});
3845

39-
for (const [key, value] of Object.entries(p)) {
40-
if (Array.isArray(value)) {
41-
for (const singleValue of value) searchParams.append(key, singleValue);
42-
} else {
43-
searchParams.append(key, (value as any));
44-
}
46+
return response.data;
47+
} catch (e) {
48+
if (e instanceof AxiosError && e.response?.data?.error) {
49+
if (e.response.status === 401) {
50+
if (this.client.config.apiKey) {
51+
throw new InvalidAPIKeyError(url);
52+
} else {
53+
throw new MissingAPIKeyError(url);
4554
}
55+
}
56+
57+
throw new FortniteAPIError(e.response.data, e.config, e.response.status);
58+
}
4659

47-
return searchParams.toString();
48-
},
60+
throw e;
61+
}
62+
}
63+
64+
public async fetchStats(url: string, params?: any): Promise<FortniteAPIResponseData> {
65+
try {
66+
const response = await this.statsAxios({
67+
url,
68+
params,
69+
paramsSerializer: serializeParams,
4970
});
5071

5172
return response.data;

src/util/util.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint-disable no-restricted-syntax */
2+
/* eslint-disable import/prefer-default-export */
3+
import { URLSearchParams } from 'url';
4+
5+
export const serializeParams = (params: any) => {
6+
const searchParams = new URLSearchParams();
7+
8+
for (const [key, value] of Object.entries(params)) {
9+
if (Array.isArray(value)) {
10+
for (const singleValue of value) searchParams.append(key, singleValue);
11+
} else {
12+
searchParams.append(key, (value as any));
13+
}
14+
}
15+
16+
return searchParams.toString();
17+
};

0 commit comments

Comments
 (0)