Describe the bug
Happy DOM has a PreflightResponseCache, but successful preflight responses are never added to it. As a result, every cross-origin request that requires a preflight sends a new OPTIONS request, even when the server provides Access-Control-Max-Age.
The existing cache structure also does not model the Fetch Standard's required cache-entry matching rules.
To Reproduce
Steps to reproduce the behavior:
-
Make a cross-origin request that requires a preflight, for example a POST with Content-Type: application/json.
-
Have the server return a successful OPTIONS response with:
Access-Control-Allow-Origin: http://localhost:1234
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
-
Repeat the same request before the 600-second max age expires.
-
Observe that Happy DOM sends OPTIONS twice.
Expected behavior
The second request should use the preflight cache and send only the actual request.
The cache should follow the Fetch Standard's CORS-preflight cache:
- Populate entries after a successful preflight.
- Match entries by network partition key, origin, URL, and credentials mode.
- Store and match allowed methods and header names independently.
- Support
Access-Control-Allow-Headers, including wildcard handling and the Authorization exception.
- Use the default
Access-Control-Max-Age of 5 seconds when the response omits the header.
- Remove expired entries and allow early eviction.
Device and details:
- OS: macOS 26.6.1
- Node version: 24.19.0
- Package version: 20.11.2
Additional context
Fetch and SyncFetch consult preflightResponseCache.get(), but neither calls preflightResponseCache.add(). The current cache is keyed only by URL and stores Access-Control Allow-Origin, methods, and an expiry, so it cannot represent the full set of spec-defined cache entries.
Describe the bug
Happy DOM has a
PreflightResponseCache, but successful preflight responses are never added to it. As a result, every cross-origin request that requires a preflight sends a newOPTIONSrequest, even when the server providesAccess-Control-Max-Age.The existing cache structure also does not model the Fetch Standard's required cache-entry matching rules.
To Reproduce
Steps to reproduce the behavior:
Make a cross-origin request that requires a preflight, for example a
POSTwithContent-Type: application/json.Have the server return a successful
OPTIONSresponse with:Repeat the same request before the 600-second max age expires.
Observe that Happy DOM sends
OPTIONStwice.Expected behavior
The second request should use the preflight cache and send only the actual request.
The cache should follow the Fetch Standard's CORS-preflight cache:
Access-Control-Allow-Headers, including wildcard handling and theAuthorizationexception.Access-Control-Max-Ageof 5 seconds when the response omits the header.Device and details:
Additional context
FetchandSyncFetchconsultpreflightResponseCache.get(), but neither callspreflightResponseCache.add(). The current cache is keyed only by URL and storesAccess-Control Allow-Origin, methods, and an expiry, so it cannot represent the full set of spec-defined cache entries.