Skip to content

Commit

Permalink
feat: Improve handling of rate limit monitoring, since it doesn't
Browse files Browse the repository at this point in the history
account for missing headers and could have false positives.
  • Loading branch information
adam-coster committed Oct 5, 2021
1 parent 1f95220 commit bc72c29
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 9 deletions.
25 changes: 19 additions & 6 deletions src/lib/clientLib/FavroClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ export class FavroClient {
* when the limit will be reset.
*/
protected _requestsRemaining = Infinity;

protected _requestsLimit = Infinity;
protected _requestsMade = 0;
protected _limitResetsAt?: Date;
private _backendId?: string;
Expand Down Expand Up @@ -167,6 +169,7 @@ export class FavroClient {
return {
total: this._requestsMade,
remaining: this._requestsRemaining,
limit: this._requestsLimit,
limitResetsAt: this._limitResetsAt,
};
}
Expand Down Expand Up @@ -216,16 +219,26 @@ export class FavroClient {
});
const res = new FavroResponse<Data, this>(this, rawResponse);
this._backendId = res.backendId || this._backendId;
this._limitResetsAt = res.limitResetsAt || this._limitResetsAt;
this._requestsRemaining =
typeof res.requestsRemaining == 'number'
? res.requestsRemaining
: this._requestsRemaining;
this._limitResetsAt = res.limitResetsAt;
this._requestsLimit = res.limit;
this._requestsRemaining = res.requestsRemaining;

if (this._requestsRemaining < 1 || res.status == 429) {
// TODO: Set an interval before allowing requests to go through again, OR SOMETHING
this._requestsRemaining = 0;
this._logger.warn(`Favro API rate limit reached!`);
this._logger.warn(
`Favro API rate limit reached! ${JSON.stringify(
{
status: res.status,
remaining: this._requestsRemaining,
made: this._requestsMade,
limit: res.limit,
resetAt: this._limitResetsAt,
},
null,
2,
)}`,
);
}
if (res.status > 299) {
this._logger.error(`Favro API Error: ${res.status}`, {
Expand Down
18 changes: 16 additions & 2 deletions src/lib/clientLib/FavroResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { URL } from 'url';
import type { FavroApi } from '$types/FavroApi.js';
import { BravoError } from '../errors.js';
import type { FavroClient } from './FavroClient.js';
import { stringToDate, stringToNumber } from '../utility.js';

function dataIsNull(data: any): data is null {
return data === null;
Expand Down Expand Up @@ -55,11 +56,24 @@ export class FavroResponse<
}

get requestsRemaining() {
return Number(this._response.headers.get('X-RateLimit-Remaining'));
return stringToNumber(this._response.headers.get('X-RateLimit-Remaining'), {
defaultIfNullish: Infinity,
customError: this._client.error,
});
}

get limitResetsAt() {
return new Date(this._response.headers.get('X-RateLimit-Reset')!);
return stringToDate(this._response.headers.get('X-RateLimit-Limit'), {
defaultIfNullish: new Date(),
customError: this._client.error,
});
}

get limit() {
return stringToNumber(this._response.headers.get('X-RateLimit-Limit'), {
defaultIfNullish: Infinity,
customError: this._client.error,
});
}

get backendId() {
Expand Down
53 changes: 52 additions & 1 deletion src/lib/utility.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ArrayMatchFunction } from '$/types/Utility.js';
import type { ArrayMatchFunction, Nullable } from '$/types/Utility.js';
import { assertBravoClaim, BravoError } from './errors.js';
import crypto from 'crypto';

Expand Down Expand Up @@ -277,3 +277,54 @@ export async function generateRandomString(
export function isNullish(value: any): value is null | undefined {
return value === null || value === undefined;
}

export function isNumber(value: any): value is number {
return typeof value === 'number';
}

export function isTruthyString(value: any): value is string {
return typeof value === 'string' && value.length > 0;
}

export function stringToDate(
dateString: Nullable<string>,
options?: { defaultIfNullish?: Date; customError?: typeof BravoError },
) {
if (isTruthyString(dateString)) {
const date = new Date(dateString);
if (isNaN(date.getTime())) {
throw new (options?.customError || BravoError)(
`Cannot convert ${dateString} to a date`,
);
}
return date;
}
if (options?.defaultIfNullish) {
return options?.defaultIfNullish;
}
throw new (options?.customError || BravoError)(
`Cannot convert ${dateString} to a date`,
);
}

/**
* Convert a numeric string to a number. If the string argument is
* nullish, throw an error unless `defaultIfNullish` is provided.
*/
export function stringToNumber(
string: Nullable<string>,
options?: { defaultIfNullish?: number; customError?: typeof BravoError },
) {
if (isTruthyString(string)) {
if (isNaN(Number(string))) {
throw new (options?.customError || BravoError)('String is not a number');
}
return +string;
}
if (options?.defaultIfNullish !== undefined) {
return options?.defaultIfNullish;
}
throw new (options?.customError || BravoError)(
`Cannot convert ${string} to a number`,
);
}
2 changes: 2 additions & 0 deletions src/types/Utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export type MimeType =
| 'text/markdown';

export type Nullish = null | undefined;

export type Nullable<T> = T | Nullish;
export type NotNullish<T> = Exclude<T, Nullish>;
export type NotNull<T> = Exclude<T, null>;
export type Defined<T> = Exclude<T, undefined>;
Expand Down

0 comments on commit bc72c29

Please sign in to comment.