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:
2025-10-20 09:59:24 +00:00
parent e228ed4ba0
commit 54afcc46e2
30 changed files with 18693 additions and 4031 deletions

105
ts/utils/deduplicator.ts Normal file
View 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();
}
}