Repository navigation
[FFL-2934] Add browser configuration fetchers - #351
Conversation
b9aeedc to
03b8806
Compare
03b8806 to
8f61547
Compare
8f61547 to
1bd9371
Compare
1bd9371 to
6313399
Compare
6313399 to
88b113c
Compare
88b113c to
aee08b1
Compare
aee08b1 to
bdc3616
Compare
| export async function fetchPrecomputedConfiguration( | ||
| options: PrecomputedConfigurationFetchOptions | ||
| ): Promise<FlagsConfiguration> { | ||
| const url = buildConfigurationUrl(options, 'precomputed') | ||
| const defaultHeaders = buildConfigurationHeaders( | ||
| options, | ||
| { | ||
| 'Content-Type': 'application/vnd.api+json', | ||
| ...(options.previousConfiguration?.precomputed?.etag && { | ||
| 'If-None-Match': options.previousConfiguration.precomputed.etag, | ||
| }), | ||
| }, | ||
| 'precomputed' | ||
| ) |
There was a problem hiding this comment.
Hey I was looking at this function for correctness. I didn't trust the codex review without writing my own test to prove it, and at that point I decided I might as well add a fix to. PTAL and merge if accepted: #364
There was a problem hiding this comment.
Thanks for putting this together. Oleksii clarified that ETags should represent the generated response. Reusing an ETag across contexts is safe when both contexts produce the same response; if the assignments differ, the server must return a different ETag.
Since the live endpoints don’t currently expose ETags or support If-None-Match through CORS, I’m going to remove ETag/304 handling from #351 and defer this. When we add it back, a 304 should reuse the response while binding it to the newly requested context.
leoromanovsky
left a comment
There was a problem hiding this comment.
Thought about etag some more and since the backend function doesn't return one anyways, nor has support for preflight we should punt on it now anyways. Do you want this support added? In terms of billing it'll still be a request to the CDN so we aren't saving customers any money but if the preflight succeeds their application will startup faster with the full network wait.
357967f to
9b34154
Compare
5cb9a76 to
567a26c
Compare
fda2716 to
efa1d35
Compare
| this.latestContextUpdate = this.retrieveFlagsConfiguration(evaluationContext, { signal }) | ||
| const configurationPromise = this.retrieveFlagsConfiguration(evaluationContext, { signal }) | ||
|
|
||
| this.latestContextUpdate = configurationPromise |
There was a problem hiding this comment.
nitpick: seems to be an unrelated change?
There was a problem hiding this comment.
Yup unrelated and unnecessary. Removed in c4f5732
| site?: string | ||
| customHeaders?: Record<string, string> | ||
| overwriteRequestHeaders?: boolean | ||
| flaggingProxy?: string |
There was a problem hiding this comment.
major (given this is now part of our public API): we've chatted with Leo in a PR adding polling and retries that it could be simpler and more flexible to allow users to pass their own fetch function instead of providing tons of configuration options. This makes the interface and our implementation simpler, as well as allow further customization (e.g. overriding URL, adding retries etc)
There was a problem hiding this comment.
Implemented in 4b46e33
Both standalone fetchers and DatadogProvider can now use a caller-supplied fetch implementation, allowing it to own retries, timeouts, proxying, or header customization.
The new standalone option types do not expose the proxy/header-specific fields. I retained them only on FlaggingInitConfiguration because they were already part of the existing provider API:
openfeature-js-client/packages/browser/src/domain/configuration.ts
Lines 49 to 62 in 02461e3
| 'DD-Client-Library-Language': sourcePayload.sdk_name, | ||
| 'DD-Client-Library-Version': sourcePayload.sdk_version, |
There was a problem hiding this comment.
minor: shall we send these to precomputed endpoint as well? they are helpful
EDIT: ok, it looks like we'd need to modify CORS first, so feel free to ignore
There was a problem hiding this comment.
Agreed, and I’ll leave these headers rules-only for now (until the precomputed endpoint’s CORS configuration allows them)
|
|
||
| const configuration = configurationFromRulesBinary(new Uint8Array(await response.arrayBuffer())) | ||
| if (configuration.rules) { | ||
| configuration.rules.fetchedAt = timeStampNow() |
There was a problem hiding this comment.
nitpick: I think the fetchedAt timestamp should be stored before we make the request. It's primarily going to be used to determine whether the client needs to refresh their configuration or the current one is fresh enough. Using an earlier timestamp is more conservative
There was a problem hiding this comment.
Done in 973dd01
Both fetchers now capture fetchedAt before starting the request. I also added regression assertions verifying that the timestamp has already been recorded when the supplied fetch implementation is invoked.
| /** @internal */ | ||
| export { parsePrecomputedConfigurationResponse } from './wire-validation' |
There was a problem hiding this comment.
minor: hm... does this attach @internal to parsePrecomputedConfigurationResponse correctly? shall we have this comment on the function definition?
There was a problem hiding this comment.
Good catch. @internal is now attached directly to the function definition:
I also removed the redundant annotation from the re-export:
| return decodeUniversalFlagConfigurationBinary(base64Decode(response)) | ||
| } | ||
|
|
||
| export function decodeUniversalFlagConfigurationBinary(response: Uint8Array): FlagsConfiguration { |
There was a problem hiding this comment.
nitpick: very opinionated but I'm trying to gradually phase out "universal flag configuration" usage in favor of simple "flags configuration." We have so many different configuration formats that calling them all "universal" doesn't make sense
There was a problem hiding this comment.
Sounds good! Renamed the protobuf helpers from decode/encodeUniversalFlagConfiguration to decode/encodeFlagsConfiguration and updated the related terminology.
| export function decodeUniversalFlagConfiguration(response: string): FlagsConfiguration { | ||
| return fromBinary(FlagsConfigurationSchema, base64Decode(response)) | ||
| return decodeUniversalFlagConfigurationBinary(base64Decode(response)) | ||
| } | ||
|
|
||
| export function decodeUniversalFlagConfigurationBinary(response: Uint8Array): FlagsConfiguration { | ||
| return fromBinary(FlagsConfigurationSchema, response) | ||
| } |
There was a problem hiding this comment.
minor: imo "protobuf" module should not care about base64 encoding/decoding the whole response. Protobuf is binary-only.
Base64 is part of configuration wire spec, so base64 encoding/decoding should happen there
There was a problem hiding this comment.
The protobuf codec now accepts and returns binary data only. Base64 decoding/encoding happens at the configuration wire boundary, while binary endpoint responses pass directly to the protobuf decoder.
…meerank/FFL-2934/configuration-fetchers-bootstrap-init # Conflicts: # packages/browser/src/domain/configuration.ts # packages/browser/src/transport/fetchConfiguration.ts
This comment has been minimized.
This comment has been minimized.
…meerank/FFL-2934/configuration-fetchers-bootstrap-init
45e3074 to
04d7a48
Compare
…meerank/FFL-2934/configuration-fetchers-bootstrap-init
OpenFeature Browser Entrypoint Bundle SizesMeasured from the Vite production output after installing packed
Default and precomputed entrypoints are expected to keep Protobuf-ES out of their bundles. Rules-based entrypoints are expected to include protobuf markers as a positive control. The marker check is a packed-artifact backstop; the source import boundary is enforced by This report shows current PR artifact sizes only; it does not compare against the base branch. |
Summary
dd-application-idonly to the precomputed endpoint; rules-based requests usedd-client-token, the protobufAcceptheader, and SDK identification headersDatadogProvider.initialFlagsConfigurationpublic API as a network-failure fallbackDatadogOfflineProvider.setConfiguration()for explicit caller-managed configuration304support until the live endpoints expose ETags and permit conditional request headers through CORSUser impact
Consumers can fetch portable configuration explicitly and load it into
DatadogOfflineProvider:The same offline-provider flow works with
fetchRulesConfigurationfrom the opt-in/rules-basedentry point.DatadogProviderremains network-first and continues to fetch precomputed configuration during initialization and context changes. For backward compatibility, it still acceptsinitialFlagsConfiguration; a matching precomputed configuration remains available as the in-memory fallback if the network request fails. Applications that want explicit offline configuration ownership can useDatadogOfflineProviderand transition to the online provider through provider composition or handoff once it is ready.This matches the React Native separation between offline configuration delivery and online fetching while leaving transition policy outside either provider.
Stack and merge order
This PR is stacked on #346. The
getPrecomputedContextwork from #353 is now included in #344.main; they can merge independently.Jira: FFL-2934
Validation
initialFlagsConfigurationremains an in-memory fallback after a network failureDatadogOfflineProviderwithout evaluation errors