feat: Implement comprehensive web request handling with caching, retry, and interceptors
- 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.
This commit is contained in:
		
							
								
								
									
										105
									
								
								ts/utils/deduplicator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								ts/utils/deduplicator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,105 @@ | ||||
| /** | ||||
|  * 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(); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user