From 1c314d61e2d48dff29f2e2c4ebf49e28aeec64c5 Mon Sep 17 00:00:00 2001 From: Daniel Fischer Date: Fri, 30 Dec 2022 00:36:51 +0100 Subject: [PATCH] partition API rate limiters by user --- packages/api/package.json | 2 +- packages/api/src/client/ApiClient.ts | 26 +++++++++++++++++------- packages/api/src/client/BaseApiClient.ts | 3 +-- packages/auth/src/AccessToken.ts | 2 -- packages/chat/package.json | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index 9c6bea765..bc1251701 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -36,7 +36,7 @@ "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/detect-node": "^3.0.1", "@d-fischer/logger": "^4.0.0", - "@d-fischer/rate-limiter": "^0.6.1", + "@d-fischer/rate-limiter": "^0.6.2", "@d-fischer/shared-utils": "^3.4.0", "@twurple/api-call": "6.0.0-pre.0", "@twurple/common": "6.0.0-pre.0", diff --git a/packages/api/src/client/ApiClient.ts b/packages/api/src/client/ApiClient.ts index 31a0f7e4c..44f03c1c1 100644 --- a/packages/api/src/client/ApiClient.ts +++ b/packages/api/src/client/ApiClient.ts @@ -1,12 +1,13 @@ import { isNode } from '@d-fischer/detect-node'; import { createLogger, type LoggerOptions } from '@d-fischer/logger'; -import { TimeBasedRateLimiter } from '@d-fischer/rate-limiter'; -import { callTwitchApiRaw, type TwitchApiCallFetchOptions, type TwitchApiCallOptions } from '@twurple/api-call'; +import { PartitionedRateLimiter, PartitionedTimeBasedRateLimiter } from '@d-fischer/rate-limiter'; +import { callTwitchApiRaw, type TwitchApiCallFetchOptions } from '@twurple/api-call'; import { type AuthProvider } from '@twurple/auth'; import { extractUserId, rtfm, type UserIdResolvable } from '@twurple/common'; import { HelixRateLimiter } from '../api/helix/HelixRateLimiter'; import { ConfigError } from '../errors/ConfigError'; import { BaseApiClient } from './BaseApiClient'; +import { type ContextApiCallOptions } from './ContextApiCallOptions'; import { UserContextApiClient } from './UserContextApiClient'; /** @@ -35,8 +36,9 @@ export interface ApiConfig { * @private */ export interface TwitchApiCallOptionsInternal { - options: TwitchApiCallOptions; + options: ContextApiCallOptions; clientId?: string; + userId?: string; accessToken?: string; authorizationType?: string; fetchOptions?: TwitchApiCallFetchOptions; @@ -65,13 +67,23 @@ export class ApiClient extends BaseApiClient { config, createLogger({ name: 'twurple:api:client', ...config.logger }), isNode - ? new HelixRateLimiter({ logger: rateLimitLoggerOptions }) - : new TimeBasedRateLimiter({ + ? new PartitionedRateLimiter({ + getPartitionKey: req => req.userId ?? null, + createChild: () => new HelixRateLimiter({ logger: rateLimitLoggerOptions }) + }) + : new PartitionedTimeBasedRateLimiter({ logger: rateLimitLoggerOptions, bucketSize: 800, timeFrame: 64000, - doRequest: async ({ options, clientId, accessToken, authorizationType, fetchOptions }) => - await callTwitchApiRaw(options, clientId, accessToken, authorizationType, fetchOptions) + doRequest: async ({ + options, + clientId, + accessToken, + authorizationType, + fetchOptions + }: TwitchApiCallOptionsInternal) => + await callTwitchApiRaw(options, clientId, accessToken, authorizationType, fetchOptions), + getPartitionKey: req => req.userId ?? null }) ); } diff --git a/packages/api/src/client/BaseApiClient.ts b/packages/api/src/client/BaseApiClient.ts index 9314c5ed4..278a2542d 100644 --- a/packages/api/src/client/BaseApiClient.ts +++ b/packages/api/src/client/BaseApiClient.ts @@ -2,7 +2,6 @@ import { Cacheable, CachedGetter } from '@d-fischer/cache-decorators'; import type { Logger } from '@d-fischer/logger'; import type { RateLimiter } from '@d-fischer/rate-limiter'; import { mapOptional } from '@d-fischer/shared-utils'; -import type { TwitchApiCallOptions } from '@twurple/api-call'; import { callTwitchApi, callTwitchApiRaw, @@ -446,7 +445,7 @@ export class BaseApiClient { } private async _callApiInternal( - options: TwitchApiCallOptions, + options: ContextApiCallOptions, clientId?: string, accessToken?: string, authorizationType?: string diff --git a/packages/auth/src/AccessToken.ts b/packages/auth/src/AccessToken.ts index 208f72eb9..0a0dbfa93 100644 --- a/packages/auth/src/AccessToken.ts +++ b/packages/auth/src/AccessToken.ts @@ -92,8 +92,6 @@ export function getExpiryDateOfAccessToken(token: ExpireableAccessToken): Date | * A one-minute grace period is applied for smooth handling of API latency. * * @param token The access token. - * - * Defaults to a minute. */ export function accessTokenIsExpired(token: ExpireableAccessToken): boolean { return mapNullable(getExpiryMillis(token), _ => Date.now() > _) ?? false; diff --git a/packages/chat/package.json b/packages/chat/package.json index 7f788d73a..4ab85d001 100644 --- a/packages/chat/package.json +++ b/packages/chat/package.json @@ -36,7 +36,7 @@ "@d-fischer/cache-decorators": "^3.0.0", "@d-fischer/deprecate": "^2.0.2", "@d-fischer/logger": "^4.0.0", - "@d-fischer/rate-limiter": "^0.6.1", + "@d-fischer/rate-limiter": "^0.6.2", "@d-fischer/shared-utils": "^3.4.0", "@d-fischer/typed-event-emitter": "^3.3.0", "@twurple/common": "6.0.0-pre.0",