/** * 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 > = 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, ): 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(); 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(); } }