/** * TimeSafari Community Features Integration * * Implements community features integration with rate limiting, backoff policies, * and polling optimization for TimeSafari's community-driven platform. * * @author Matthew Raymer * @version 1.0.0 */ import { TimeSafariIntegrationService, TimeSafariUserConfig, TimeSafariNotificationBundle } from './timesafari-integration'; /** * TimeSafari Community Integration Service * * Handles community features integration with intelligent rate limiting, * backoff policies, and polling optimization. */ export class TimeSafariCommunityIntegrationService { private static instance: TimeSafariCommunityIntegrationService; private integrationService: TimeSafariIntegrationService; private rateLimiter: RateLimiter; private backoffManager: BackoffManager; private pollingManager: PollingManager; private constructor() { this.integrationService = TimeSafariIntegrationService.getInstance(); this.rateLimiter = new RateLimiter(); this.backoffManager = new BackoffManager(); this.pollingManager = new PollingManager(); } /** * Get singleton instance */ static getInstance(): TimeSafariCommunityIntegrationService { if (!TimeSafariCommunityIntegrationService.instance) { TimeSafariCommunityIntegrationService.instance = new TimeSafariCommunityIntegrationService(); } return TimeSafariCommunityIntegrationService.instance; } /** * Initialize community integration * * @param config - Community integration configuration */ async initialize(config: CommunityIntegrationConfig): Promise { try { // Initializing TimeSafari community integration // Initialize rate limiter this.rateLimiter.configure({ maxRequestsPerMinute: config.maxRequestsPerMinute || 30, maxRequestsPerHour: config.maxRequestsPerHour || 1000, burstLimit: config.burstLimit || 10 }); // Initialize backoff manager this.backoffManager.configure({ initialDelayMs: config.initialBackoffMs || 1000, maxDelayMs: config.maxBackoffMs || 30000, backoffMultiplier: config.backoffMultiplier || 2, jitterEnabled: config.jitterEnabled !== false }); // Initialize polling manager this.pollingManager.configure({ baseIntervalMs: config.basePollingIntervalMs || 300000, // 5 minutes maxIntervalMs: config.maxPollingIntervalMs || 1800000, // 30 minutes adaptivePolling: config.adaptivePolling !== false }); // TimeSafari community integration initialization complete } catch (error) { throw new Error(`TimeSafari community integration initialization failed: ${error}`); } } /** * Fetch community data with rate limiting and backoff * * @param config - User configuration * @returns Community data bundle */ async fetchCommunityDataWithRateLimit(config: TimeSafariUserConfig): Promise { try { // Fetching community data with rate limiting // Check rate limits if (!this.rateLimiter.canMakeRequest()) { const waitTime = this.rateLimiter.getWaitTime(); // Rate limit exceeded, waiting await this.sleep(waitTime); } // Check backoff status if (this.backoffManager.isInBackoff()) { const backoffTime = this.backoffManager.getRemainingBackoffTime(); // In backoff, waiting await this.sleep(backoffTime); } // Record request attempt this.rateLimiter.recordRequest(); try { // Fetch data const bundle = await this.integrationService.fetchCommunityData(config); // Reset backoff on success this.backoffManager.recordSuccess(); // Update polling interval based on success this.pollingManager.recordSuccess(); // Community data fetch successful return bundle; } catch (error) { // Record failure and apply backoff this.backoffManager.recordFailure(); this.pollingManager.recordFailure(); throw new Error(`Community data fetch failed: ${error}`); } } catch (error) { throw new Error(`Rate-limited fetch failed: ${error}`); } } /** * Get optimized polling interval * * @returns Polling interval in milliseconds */ getOptimizedPollingInterval(): number { return this.pollingManager.getCurrentInterval(); } /** * Get rate limit status * * @returns Rate limit status */ getRateLimitStatus(): RateLimitStatus { return this.rateLimiter.getStatus(); } /** * Get backoff status * * @returns Backoff status */ getBackoffStatus(): BackoffStatus { return this.backoffManager.getStatus(); } /** * Get polling status * * @returns Polling status */ getPollingStatus(): PollingStatus { return this.pollingManager.getStatus(); } /** * Sleep utility * * @param ms - Milliseconds to sleep */ private sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } } /** * Rate Limiter */ class RateLimiter { private config: RateLimitConfig = { maxRequestsPerMinute: 30, maxRequestsPerHour: 1000, burstLimit: 10 }; private requests: number[] = []; private burstRequests: number[] = []; configure(config: RateLimitConfig): void { this.config = { ...this.config, ...config }; } canMakeRequest(): boolean { const now = Date.now(); const oneMinuteAgo = now - 60000; const oneHourAgo = now - 3600000; const oneSecondAgo = now - 1000; // Clean old requests this.requests = this.requests.filter(time => time > oneHourAgo); this.burstRequests = this.burstRequests.filter(time => time > oneSecondAgo); // Check limits const requestsLastMinute = this.requests.filter(time => time > oneMinuteAgo).length; const requestsLastHour = this.requests.length; const burstRequests = this.burstRequests.length; return requestsLastMinute < this.config.maxRequestsPerMinute && requestsLastHour < this.config.maxRequestsPerHour && burstRequests < this.config.burstLimit; } recordRequest(): void { const now = Date.now(); this.requests.push(now); this.burstRequests.push(now); } getWaitTime(): number { const now = Date.now(); const oneMinuteAgo = now - 60000; const oneHourAgo = now - 3600000; const oneSecondAgo = now - 1000; // Find oldest request in each window const requestsLastMinute = this.requests.filter(time => time > oneMinuteAgo); const requestsLastHour = this.requests.filter(time => time > oneHourAgo); const burstRequests = this.burstRequests.filter(time => time > oneSecondAgo); let waitTime = 0; // Check minute limit if (requestsLastMinute.length >= this.config.maxRequestsPerMinute) { const oldestInMinute = Math.min(...requestsLastMinute); waitTime = Math.max(waitTime, oldestInMinute + 60000 - now); } // Check hour limit if (requestsLastHour.length >= this.config.maxRequestsPerHour) { const oldestInHour = Math.min(...requestsLastHour); waitTime = Math.max(waitTime, oldestInHour + 3600000 - now); } // Check burst limit if (burstRequests.length >= this.config.burstLimit) { const oldestBurst = Math.min(...burstRequests); waitTime = Math.max(waitTime, oldestBurst + 1000 - now); } return Math.max(0, waitTime); } getStatus(): RateLimitStatus { const now = Date.now(); const oneMinuteAgo = now - 60000; const oneHourAgo = now - 3600000; const oneSecondAgo = now - 1000; return { requestsLastMinute: this.requests.filter(time => time > oneMinuteAgo).length, requestsLastHour: this.requests.filter(time => time > oneHourAgo).length, burstRequests: this.burstRequests.filter(time => time > oneSecondAgo).length, canMakeRequest: this.canMakeRequest(), waitTime: this.getWaitTime() }; } } /** * Backoff Manager */ class BackoffManager { private config: BackoffConfig = { initialDelayMs: 1000, maxDelayMs: 30000, backoffMultiplier: 2, jitterEnabled: true }; private failureCount = 0; private lastFailureTime = 0; private currentDelay = 0; configure(config: BackoffConfig): void { this.config = { ...this.config, ...config }; } recordSuccess(): void { this.failureCount = 0; this.currentDelay = 0; } recordFailure(): void { this.failureCount++; this.lastFailureTime = Date.now(); // Calculate exponential backoff this.currentDelay = Math.min( this.config.initialDelayMs * Math.pow(this.config.backoffMultiplier, this.failureCount - 1), this.config.maxDelayMs ); // Add jitter if enabled if (this.config.jitterEnabled) { const jitter = Math.random() * 0.1 * this.currentDelay; this.currentDelay += jitter; } } isInBackoff(): boolean { if (this.failureCount === 0) return false; const timeSinceLastFailure = Date.now() - this.lastFailureTime; return timeSinceLastFailure < this.currentDelay; } getRemainingBackoffTime(): number { if (!this.isInBackoff()) return 0; const timeSinceLastFailure = Date.now() - this.lastFailureTime; return this.currentDelay - timeSinceLastFailure; } getStatus(): BackoffStatus { return { failureCount: this.failureCount, currentDelay: this.currentDelay, isInBackoff: this.isInBackoff(), remainingBackoffTime: this.getRemainingBackoffTime() }; } } /** * Polling Manager */ class PollingManager { private config: PollingConfig = { baseIntervalMs: 300000, // 5 minutes maxIntervalMs: 1800000, // 30 minutes adaptivePolling: true }; private currentInterval = 300000; private consecutiveFailures = 0; private consecutiveSuccesses = 0; configure(config: PollingConfig): void { this.config = { ...this.config, ...config }; this.currentInterval = this.config.baseIntervalMs; } recordSuccess(): void { this.consecutiveSuccesses++; this.consecutiveFailures = 0; if (this.config.adaptivePolling && this.consecutiveSuccesses >= 3) { // Decrease interval on sustained success this.currentInterval = Math.max( this.config.baseIntervalMs, this.currentInterval * 0.8 ); } } recordFailure(): void { this.consecutiveFailures++; this.consecutiveSuccesses = 0; if (this.config.adaptivePolling) { // Increase interval on failure this.currentInterval = Math.min( this.config.maxIntervalMs, this.currentInterval * 1.5 ); } } getCurrentInterval(): number { return this.currentInterval; } getStatus(): PollingStatus { return { currentInterval: this.currentInterval, consecutiveFailures: this.consecutiveFailures, consecutiveSuccesses: this.consecutiveSuccesses, adaptivePolling: this.config.adaptivePolling }; } } /** * Configuration Interfaces */ export interface CommunityIntegrationConfig { maxRequestsPerMinute?: number; maxRequestsPerHour?: number; burstLimit?: number; initialBackoffMs?: number; maxBackoffMs?: number; backoffMultiplier?: number; jitterEnabled?: boolean; basePollingIntervalMs?: number; maxPollingIntervalMs?: number; adaptivePolling?: boolean; } interface RateLimitConfig { maxRequestsPerMinute: number; maxRequestsPerHour: number; burstLimit: number; } interface BackoffConfig { initialDelayMs: number; maxDelayMs: number; backoffMultiplier: number; jitterEnabled: boolean; } interface PollingConfig { baseIntervalMs: number; maxIntervalMs: number; adaptivePolling: boolean; } /** * Status Interfaces */ export interface RateLimitStatus { requestsLastMinute: number; requestsLastHour: number; burstRequests: number; canMakeRequest: boolean; waitTime: number; } export interface BackoffStatus { failureCount: number; currentDelay: number; isInBackoff: boolean; remainingBackoffTime: number; } export interface PollingStatus { currentInterval: number; consecutiveFailures: number; consecutiveSuccesses: number; adaptivePolling: boolean; }