- Added cache strategies: NetworkFirst, CacheFirst, StaleWhileRevalidate, NetworkOnly, and CacheOnly. - Introduced InterceptorManager for managing request, response, and error interceptors. - Developed RetryManager for handling request retries with customizable backoff strategies. - Implemented RequestDeduplicator to prevent simultaneous identical requests. - Created timeout utilities for handling request timeouts. - Enhanced WebrequestClient to support global interceptors, caching, and retry logic. - Added convenience methods for common HTTP methods (GET, POST, PUT, DELETE) with JSON handling. - Established a fetch-compatible webrequest function for seamless integration. - Defined core type structures for caching, retry options, interceptors, and web request configurations.
		
			
				
	
	
		
			106 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			106 lines
		
	
	
		
			2.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * Request deduplication system
 | |
|  * Prevents multiple simultaneous identical requests
 | |
|  */
 | |
| 
 | |
| import * as plugins from '../webrequest.plugins.js';
 | |
| 
 | |
| export class RequestDeduplicator {
 | |
|   private inFlightRequests: Map<
 | |
|     string,
 | |
|     plugins.smartpromise.Deferred<Response>
 | |
|   > = new Map();
 | |
| 
 | |
|   /**
 | |
|    * Generate a deduplication key from a request
 | |
|    */
 | |
|   public generateKey(request: Request): string {
 | |
|     // Use URL + method as the base key
 | |
|     const url = request.url;
 | |
|     const method = request.method;
 | |
| 
 | |
|     // For GET/HEAD requests, just use URL + method
 | |
|     if (method === 'GET' || method === 'HEAD') {
 | |
|       return `${method}:${url}`;
 | |
|     }
 | |
| 
 | |
|     // For other methods, we can't deduplicate as easily
 | |
|     // (body might be different)
 | |
|     // Use a timestamp to make it unique
 | |
|     return `${method}:${url}:${Date.now()}`;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Execute a request with deduplication
 | |
|    */
 | |
|   public async execute(
 | |
|     key: string,
 | |
|     executeFn: () => Promise<Response>,
 | |
|   ): Promise<{ response: Response; wasDeduplicated: boolean }> {
 | |
|     // Check if request is already in flight
 | |
|     const existingDeferred = this.inFlightRequests.get(key);
 | |
| 
 | |
|     if (existingDeferred) {
 | |
|       // Wait for the existing request to complete
 | |
|       const response = await existingDeferred.promise;
 | |
| 
 | |
|       // Clone the response so it can be used multiple times
 | |
|       return {
 | |
|         response: response.clone(),
 | |
|         wasDeduplicated: true,
 | |
|       };
 | |
|     }
 | |
| 
 | |
|     // Create a new deferred for this request
 | |
|     const deferred = plugins.smartpromise.defer<Response>();
 | |
|     this.inFlightRequests.set(key, deferred);
 | |
| 
 | |
|     try {
 | |
|       // Execute the request
 | |
|       const response = await executeFn();
 | |
| 
 | |
|       // Resolve the deferred
 | |
|       deferred.resolve(response);
 | |
| 
 | |
|       // Clean up
 | |
|       this.inFlightRequests.delete(key);
 | |
| 
 | |
|       // Return the original response
 | |
|       return {
 | |
|         response,
 | |
|         wasDeduplicated: false,
 | |
|       };
 | |
|     } catch (error) {
 | |
|       // Reject the deferred
 | |
|       deferred.reject(error);
 | |
| 
 | |
|       // Clean up
 | |
|       this.inFlightRequests.delete(key);
 | |
| 
 | |
|       // Re-throw the error
 | |
|       throw error;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Check if a request is currently in flight
 | |
|    */
 | |
|   public isInFlight(key: string): boolean {
 | |
|     return this.inFlightRequests.has(key);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Get the number of in-flight requests
 | |
|    */
 | |
|   public getInFlightCount(): number {
 | |
|     return this.inFlightRequests.size;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Clear all in-flight requests
 | |
|    */
 | |
|   public clear(): void {
 | |
|     this.inFlightRequests.clear();
 | |
|   }
 | |
| }
 |