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(); | ||
|  |   } | ||
|  | } |