All files / app/http rate-limit.interceptor.ts

100% Statements 17/17
88.23% Branches 15/17
100% Functions 2/2
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42  1x       1x     7x         9x   8x   8x   6x   6x 6x   6x         2x 2x 2x       1x            
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, retry, timer } from 'rxjs';
 
@Injectable()
export class RateLimitInterceptor implements HttpInterceptor {
 
  intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(request).pipe(
      retry({
        count: 3,
        delay: (error: HttpErrorResponse, retryCount: number) => {
          // Check if status code is 503 (Service Unavailable) or 429 (Too Many Requests)
          if (error.status === 503 || error.status === 429) {
            // Try to read X-RateLimit-Retry-After header
            const retryAfterHeader = error.headers?.get('X-RateLimit-Retry-After');
 
            if (retryAfterHeader) {
              // Parse the header value - it should be in seconds (can be decimal like 3.5)
              const retryAfterSeconds = parseFloat(retryAfterHeader);
 
              Eif (!isNaN(retryAfterSeconds) && retryAfterSeconds > 0) {
                console.warn(`Rate limit hit. Retrying after ${retryAfterSeconds} seconds`);
                // Convert seconds to milliseconds for timer
                return timer(retryAfterSeconds * 1000);
              }
            }
 
            // If no valid header, use exponential backoff (1s, 2s, 4s)
            const backoffDelay = Math.pow(2, retryCount - 1) * 1000;
            console.warn(`Rate limit hit. Retrying with exponential backoff: ${backoffDelay}ms`);
            return timer(backoffDelay);
          }
 
          // For other errors, don't retry
          throw error;
        },
      })
    );
  }
}