You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

455 lines
12 KiB

/**
* 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<void> {
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<TimeSafariNotificationBundle> {
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<void> {
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;
}