diff --git a/src/reddit/session/SessionManager.ts b/src/reddit/session/SessionManager.ts index 1dafa32..2ffc15e 100644 --- a/src/reddit/session/SessionManager.ts +++ b/src/reddit/session/SessionManager.ts @@ -1,18 +1,33 @@ -import axios, { AxiosInstance, AxiosError } from 'axios'; +import axios, { AxiosInstance, AxiosError, AxiosResponse, AxiosRequestConfig } from 'axios'; const qs = require('qs'); import dotenv from 'dotenv'; import axiosRetry from 'axios-retry'; -import axiosThrottle from 'axios-request-throttle'; +import Bottleneck from 'bottleneck'; import { DatabaseService } from '../../db/services/Database'; dotenv.config(); class RedditSessionManager { private static instance: RedditSessionManager; - public axiosInstance: AxiosInstance; + public readonly axiosInstance: AxiosInstance; + private limiter: Bottleneck; private constructor() { - axiosThrottle.use(axios, { requestsPerSecond: 1 }); // Throttle setup + + // Initialize the Bottleneck limiter + this.limiter = new Bottleneck({ + id: "reddit-limiter", + datastore: "ioredis", + clearDatastore: false, + clientOptions: { + host: process.env.REDIS_HOST, + port: Number(process.env.REDIS_PORT), + password: process.env.REDIS_PASSWORD || undefined, // Use undefined if no password is set + enableOfflineQueue: true + }, + maxConcurrent: 1, // Maximum number of concurrent requests + minTime: 1000 // Minimum time between dispatches of requests in milliseconds + }); this.axiosInstance = axios.create({ baseURL: 'https://oauth.reddit.com/', // Base URL for OAuth2 Reddit API @@ -20,6 +35,10 @@ class RedditSessionManager { 'User-Agent': 'CrossTalk PM/0.1 by Whitneywisconson' } }); + + // Wrap axios requests with the limiter + this.wrapAxiosInstance(this.axiosInstance); + axiosRetry(this.axiosInstance, { retries: 3, retryDelay: this.retryDelayStrategy, @@ -35,6 +54,10 @@ class RedditSessionManager { return RedditSessionManager.instance; } + public async shutdown(): Promise { + await this.limiter.disconnect(); + } + private async initializeAuthentication() { // Check the database for an existing token const currentToken = await DatabaseService.getCurrentOAuthToken(this.axiosInstance.defaults.baseURL as string); @@ -103,6 +126,32 @@ class RedditSessionManager { const status = error.response?.status ?? 0; return status === 429 || status >= 400; } + + private wrapAxiosInstance(instance: AxiosInstance): void { + // Wrap the get method + const originalGet = instance.get; + instance.get = >(url: string, config?: AxiosRequestConfig): Promise => { + return this.limiter.schedule(() => originalGet.apply(instance, [url, config])) as Promise; + }; + + // Wrap the post method + const originalPost = instance.post; + instance.post = >(url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return this.limiter.schedule(() => originalPost.apply(instance, [url, data, config])) as Promise; + }; + + // Wrap the put method + const originalPut = instance.put; + instance.put = >(url: string, data?: any, config?: AxiosRequestConfig): Promise => { + return this.limiter.schedule(() => originalPut.apply(instance, [url, data, config])) as Promise; + }; + + // Wrap the delete method + const originalDelete = instance.delete; + instance.delete = >(url: string, config?: AxiosRequestConfig): Promise => { + return this.limiter.schedule(() => originalDelete.apply(instance, [url, config])) as Promise; + }; + } } export default RedditSessionManager;