Add IndexedDB persistence for browser flag configurations#186
Add IndexedDB persistence for browser flag configurations#186leoromanovsky merged 9 commits intomainfrom
Conversation
Store precomputed flag assignments in IndexedDB so the browser SDK can serve cached flags across page loads, surviving short backend outages (e.g., CDN maintenance windows) instead of falling back to programmatic defaults.
There was a problem hiding this comment.
Pull request overview
This PR adds IndexedDB-based persistence for flag configurations in the browser SDK, bringing it to feature parity with the iOS and Android SDKs. When the network is unavailable during initialization, the provider can now fall back to previously cached flags instead of failing, making the SDK more resilient to temporary outages.
Changes:
- Added IndexedDBFlagsCache class to persist and retrieve flag configurations from IndexedDB
- Modified DatadogProvider to load cached flags on initialization and persist successful network fetches
- Added comprehensive test coverage for cache operations and fallback behavior
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/browser/src/cache/indexeddb-flags-cache.ts | New cache implementation for storing flag configurations in IndexedDB with graceful error handling |
| packages/browser/src/cache/helpers.ts | Added hasIndexedDB() helper for feature detection with try-catch protection |
| packages/browser/src/openfeature/provider.ts | Integrated cache loading on initialization and persistence after successful fetches, with fallback logic when network fails |
| packages/browser/test/cache/indexeddb-flags-cache.spec.ts | Comprehensive unit tests for cache operations including error scenarios and data validation |
| packages/browser/test/openfeature/provider-persistence.spec.ts | Integration tests verifying persistence behavior, network-first strategy, and graceful degradation |
| packages/browser/package.json | Added fake-indexeddb dev dependency for testing |
| yarn.lock | Added fake-indexeddb package resolution |
| LICENSE-3rdparty.csv | Added fake-indexeddb license attribution |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Resolve on tx.oncomplete instead of request.onsuccess so that writes are guaranteed to be persisted. A request can succeed but the transaction can still fail (e.g. quota errors), which would silently lose data under the old pattern.
| private flagsConfiguration: FlagsConfiguration = {} | ||
| private configuration?: FlaggingConfiguration | ||
| private exposureCache: AssignmentCache | undefined | ||
| private flagsCache: IndexedDBFlagsCache | undefined |
There was a problem hiding this comment.
I'll probably refactor this into an abstract type
greghuels
left a comment
There was a problem hiding this comment.
It's probably worth addressing the comments on how this.flagsConfiguration is set.
…fig guards Prevent cross-user flag leakage on shared computers by validating targetingKey before using cached data, skip cache when caller provides initialFlagsConfiguration, and isolate cache entries per SDK key.
…cache, STALE status - Store FlagsConfiguration objects directly in IndexedDB (no JSON stringify/parse — IndexedDB uses the structured clone algorithm). - Key cache entries by clientToken + hashed EvaluationContext, so different contexts don't collide. Removes the targetingKey comparison guard in provider.ts. - Make set() fire-and-forget — cache writes never block initialize or onContextChange, and never put the provider into ERROR state. - Exposure cache init runs concurrently with the network fetch. - Emit ProviderStatus.STALE / ProviderEvents.Stale when serving cached config after a fetch failure (instead of READY).
- Cache read now runs concurrently with the network fetch instead of
blocking it. Cached config is only used as fallback when fetch fails.
- Use full MD5 hash for context key (no truncation) to avoid collisions.
- Sort context keys before hashing so {a:1, b:2} and {b:2, a:1}
produce the same cache key.
Motivation
The browser SDK currently has no disk persistence for flag configurations. If the network is unavailable during a page load (backend outage, flaky connection, CDN maintenance), users get no flags and fall back to programmatic defaults. iOS and Android SDKs both have disk-based caching that makes them resilient to short outages — this brings the browser SDK to parity.
Changes
The provider now stores the full precomputed flag configuration as a JSON blob in IndexedDB after every successful fetch. On the next page load, it reads the cached configuration from IndexedDB before hitting the network. If the network request fails but cached flags exist, the provider goes to READY state using the cached data instead of throwing an error. If IndexedDB is unavailable (private browsing, older browsers), the SDK works exactly as it does today — no behavior change.
Decisions
JSON.stringify/JSON.parseinstead of the wire format — The existingconfigurationToString/configurationFromStringhas a round-trip bug where it nests the response one level deep on deserialization. Direct serialization avoids this.initialize()when the network fails.flagsCacheis undefined and all optional-chain calls are no-ops.Question for reviewers
This is a non-breaking additive feature (no API changes, no behavior change when IndexedDB is unavailable). Should this ship as a patch release or a minor release?