Skip to content

CORS preflight response cache is not implemented per Fetch Standard #2288

Description

@rexxars

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:

  1. Make a cross-origin request that requires a preflight, for example a POST with Content-Type: application/json.

  2. 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
  3. Repeat the same request before the 600-second max age expires.

  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions