Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"dependencies": {
"axios": "^0.27.2",
"axios-rate-limit": "^1.3.0",
"tslib": "^2.4.0"
}
}
12 changes: 11 additions & 1 deletion resources/structs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ export interface ClientConfig {
apiKey?: string;

/**
* The default language for all endpoints. Defaults to 'en'
* The default language for all endpoints. Defaults to `en`
*/
language: Language;

/**
* Extra timeout for stats ratelimits. Defaults to `0`.
*
* Normally the client will send 3 stats requests per 1100 milliseconds.
* Setting this option to `100` increases the rate to 3 per 1200 milliseconds.
*
* You should increase this option if you're getting 429 responses
*/
rateLimitExtraTimeout: number;
}

export interface ClientOptions extends Partial<ClientConfig> {}
5 changes: 3 additions & 2 deletions src/client/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class Client {
constructor(config?: ClientOptions) {
this.config = {
language: Language.English,
rateLimitExtraTimeout: 0,
...config,
};

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

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

Expand Down
43 changes: 32 additions & 11 deletions src/http/HTTP.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
/* eslint-disable no-restricted-syntax */
import axios, { AxiosError, AxiosInstance } from 'axios';
import { URLSearchParams } from 'url';
import rateLimit, { RateLimitedAxiosInstance } from 'axios-rate-limit';
import { version } from '../../package.json';
import Client from '../client/Client';
import FortniteAPIError from '../exceptions/FortniteAPIError';
import InvalidAPIKeyError from '../exceptions/InvalidAPIKeyError';
import MissingAPIKeyError from '../exceptions/MissingAPIKeyError';
import { serializeParams } from '../util/util';
import { FortniteAPIResponseData } from './httpStructs';

class HTTP {
public client: Client;
public axios: AxiosInstance;
public statsAxios: RateLimitedAxiosInstance;
constructor(client: Client) {
this.client = client;

Expand All @@ -26,26 +28,45 @@ class HTTP {
} : {},
},
});

this.statsAxios = rateLimit(this.axios, {
maxRequests: 3,
perMilliseconds: 1100 + this.client.config.rateLimitExtraTimeout,
});
}

public async fetch(url: string, params?: any): Promise<FortniteAPIResponseData> {
try {
const response = await this.axios({
url,
params,
paramsSerializer: (p) => {
const searchParams = new URLSearchParams();
paramsSerializer: serializeParams,
});

for (const [key, value] of Object.entries(p)) {
if (Array.isArray(value)) {
for (const singleValue of value) searchParams.append(key, singleValue);
} else {
searchParams.append(key, (value as any));
}
return response.data;
} catch (e) {
if (e instanceof AxiosError && e.response?.data?.error) {
if (e.response.status === 401) {
if (this.client.config.apiKey) {
throw new InvalidAPIKeyError(url);
} else {
throw new MissingAPIKeyError(url);
}
}

throw new FortniteAPIError(e.response.data, e.config, e.response.status);
}

return searchParams.toString();
},
throw e;
}
}

public async fetchStats(url: string, params?: any): Promise<FortniteAPIResponseData> {
try {
const response = await this.statsAxios({
url,
params,
paramsSerializer: serializeParams,
});

return response.data;
Expand Down
17 changes: 17 additions & 0 deletions src/util/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/* eslint-disable no-restricted-syntax */
/* eslint-disable import/prefer-default-export */
import { URLSearchParams } from 'url';

export const serializeParams = (params: any) => {
const searchParams = new URLSearchParams();

for (const [key, value] of Object.entries(params)) {
if (Array.isArray(value)) {
for (const singleValue of value) searchParams.append(key, singleValue);
} else {
searchParams.append(key, (value as any));
}
}

return searchParams.toString();
};