Skip to content

Chore : Add client-side rate limiting and caching for Planet API calls #5096

@shoryabansalgithub

Description

@shoryabansalgithub

Current Behavior

The Planet feature (community project sharing) makes API calls to the server (ServerInterface.js) without any client-side rate limiting, request throttling, or caching strategy. Currently:

  • Every scroll to 75% of page height triggers loadMoreProjects() without throttling
  • Search input triggers API calls with only a 250ms debounce
  • No caching of downloaded projects in IndexedDB for offline access
  • No retry logic with exponential backoff on failures
  • Projects are re-downloaded when revisiting the Planet page

Relevant files:

  • planet/js/ServerInterface.js - All API calls go through request() method without rate limiting
  • planet/js/GlobalPlanet.js - loadMoreProjects() and search() have no request throttling
  • js/planetInterface.js - Entry point for Planet functionality

Desired Behavior

Implement a robust client-side API management layer:

  1. Request Throttling

    • Add minimum delay between consecutive API calls (e.g., 500ms)
    • Prevent duplicate concurrent requests to the same endpoint
    • Queue requests when rate limit is reached
  2. Local Caching with IndexedDB

    • Cache project metadata and thumbnails locally
    • Cache full project data for recently viewed projects
    • Implement cache expiration (e.g., 24 hours for metadata)
    • Enable browsing previously viewed projects offline
  3. Retry Logic with Exponential Backoff

    • Retry failed requests with increasing delays (1s, 2s, 4s...)
    • Maximum retry limit (e.g., 3 attempts)
    • Show user-friendly error after all retries fail
  4. Request Deduplication

    • Prevent the same project from being downloaded multiple times simultaneously
    • Coalesce multiple requests for the same resource

Screenshots / Mockups

N/A - This is a backend/infrastructure improvement

Implementation

Proposed approach:

  1. Create a new RequestManager class in planet/js/:
class RequestManager {
    constructor(options = {}) {
        this.minDelay = options.minDelay || 500;
        this.maxRetries = options.maxRetries || 3;
        this.pendingRequests = new Map();
        this.lastRequestTime = 0;
    }
    
    async throttledRequest(key, requestFn) {
        // Check for pending duplicate request
        if (this.pendingRequests.has(key)) {
            return this.pendingRequests.get(key);
        }
        
        // Apply throttling
        const now = Date.now();
        const timeSinceLastRequest = now - this.lastRequestTime;
        if (timeSinceLastRequest < this.minDelay) {
            await new Promise(r => setTimeout(r, this.minDelay - timeSinceLastRequest));
        }
        
        // Execute with retry logic
        const promise = this.executeWithRetry(requestFn);
        this.pendingRequests.set(key, promise);
        this.lastRequestTime = Date.now();
        
        try {
            return await promise;
        } finally {
            this.pendingRequests.delete(key);
        }
    }
    
    async executeWithRetry(requestFn, attempt = 0) {
        try {
            return await requestFn();
        } catch (error) {
            if (attempt < this.maxRetries) {
                const delay = Math.pow(2, attempt) * 1000;
                await new Promise(r => setTimeout(r, delay));
                return this.executeWithRetry(requestFn, attempt + 1);
            }
            throw error;
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions