Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
752b828
docs: add plan for provider.setConfiguration offline init (FFL-2666)
btthomas Jul 6, 2026
9fe8db0
docs(plan): correct wire format to version 1 and add server branch
btthomas Jul 6, 2026
a9af1e8
docs(plan): make configurationFromString lenient, not throwing
btthomas Jul 6, 2026
1ae02ea
docs(plan): assume shipped PrecomputedFlag shape, note format discrep…
btthomas Jul 6, 2026
e2c86cb
docs(plan): port reference helpers (wire + configMatchesContext) vs d…
btthomas Jul 6, 2026
90ab9f8
docs(plan): drop certainty-asserting section headings
btthomas Jul 6, 2026
27227f5
docs(plan): ground precomputed shape in a sample CDN response
btthomas Jul 6, 2026
07328de
docs(plan): document native tracking path and setEvaluationContext fetch
btthomas Jul 7, 2026
5d40062
docs(plan): address review feedback
btthomas Jul 7, 2026
66a815d
docs(plan): add fetchPolicy to opt out of fetch on setEvaluationContext
btthomas Jul 7, 2026
6bbdb16
docs(plan): add 4-PR delivery plan and repurpose FFL-2691 to RUM FIT e2e
btthomas Jul 7, 2026
9683894
docs(plan): avoid FlagsConfiguration name collision (H1)
btthomas Jul 7, 2026
03d432a
docs(plan): separate config-kind axis from fetchPolicy (H2, H3)
btthomas Jul 7, 2026
3c8596b
docs(plan): add obfuscation guard to the decoder (H6)
btthomas Jul 7, 2026
7f9442f
docs(plan): fetchPolicy options object + depend on shared rules evalu…
btthomas Jul 7, 2026
1e39a24
docs(plan): add review-driven acceptance criteria (implementation mus…
btthomas Jul 7, 2026
5a9fa8f
docs(plan): add per-step meta-plan for future context-free agents
btthomas Jul 7, 2026
09a01cd
docs(plan): add per-PR branch naming convention
btthomas Jul 7, 2026
09e0fdd
Update provider_set_configuration.md
btthomas Jul 7, 2026
2654cb8
docs(plan): add open question on persisting loaded config to disk
btthomas Jul 7, 2026
4c86406
docs(plan): reconcile variationType accepted set with integer/float
btthomas Jul 7, 2026
492e1a9
docs(plan): replace fetchPolicy with an OfflineProvider plan
btthomas Jul 7, 2026
9473b0f
feat(flags): add ConfigurationWire parse + precomputed decode (FFL-26…
btthomas Jul 7, 2026
dae07c3
fix(flags): harden precomputed decode against __proto__ keys and arra…
btthomas Jul 7, 2026
b020b17
refactor(flags): tighten numeric validation, document serialId/metada…
btthomas Jul 7, 2026
894b1fc
refactor(flags): pin ConfigurationWire version to literal 1 (PR1 review)
btthomas Jul 8, 2026
3e9edd3
refactor(flags): remove speculative server wire branch (PR1 review)
btthomas Jul 8, 2026
988e93e
refactor(flags): harden precomputed decode and log wire parse failure…
btthomas Jul 8, 2026
840ff38
fix(flags): accept any JSON value for object-typed flags (PR1 review)
btthomas Jul 8, 2026
3f7c10a
refactor(flags): reuse @datadog/flagging-core wire types and parser (…
btthomas Jul 8, 2026
9b1487f
refactor(flags): adopt flagging-core 2.0.0 and reuse configurationToS…
btthomas Jul 16, 2026
267e864
refactor(flags): decodePrecomputedFlags returns a Map (PR1 review)
btthomas Jul 17, 2026
5216cac
chore(flags): pin @datadog/flagging-core to exact 2.0.0 (PR1 review)
btthomas Jul 20, 2026
cc71440
chore(flags): allow patch updates for @datadog/flagging-core (~2.0.1)
btthomas Jul 22, 2026
21524fb
feat(flags): FlagsClient.setConfiguration with context matching (FFL-…
btthomas Jul 7, 2026
b2cfdc2
refactor(flags): address PR2 review (rules seam, proto-safe context, …
btthomas Jul 7, 2026
db42f71
feat(flags): add FlagsClient.setEvaluationContextWithoutFetching
btthomas Jul 7, 2026
f746fc9
refactor(flags): ignore a mismatched runtime context for offline prec…
btthomas Jul 17, 2026
8a5d4c4
docs(flags): drop stale context/server comments in FlagsClient (PR2)
btthomas Jul 17, 2026
190a040
refactor(flags): address PR2 review findings (dead code, wire safety,…
btthomas Jul 17, 2026
084a09a
refactor(flags): store flagsCache as a Map to avoid prototype-chain l…
btthomas Jul 17, 2026
41ab2ac
refactor(flags): model ConfigurationStatus as an as-const object (PR2…
btthomas Jul 20, 2026
af0cb9d
refactor(flags): error on context mismatch for offline precomputed (PR2)
btthomas Jul 21, 2026
0c77f19
fix(flags): don't error when replacing an offline snapshot for a new …
btthomas Jul 21, 2026
68a929b
fix(flags): drop the offline overlay when entering online mode
btthomas Jul 21, 2026
67aa667
fix(flags): reject structurally malformed precomputed responses
btthomas Jul 21, 2026
f7e019f
test(flags): assert exposure attribution on replacement + malformed e…
btthomas Jul 22, 2026
2b8a643
feat(openfeature): add DatadogOfflineOpenFeatureProvider (FFL-2689, F…
btthomas Jul 7, 2026
89849cf
refactor(openfeature): extract DatadogCoreOpenFeatureProvider base
btthomas Jul 8, 2026
43ac1b5
fix(openfeature): address PR3 review (empty-context stamping, events,…
btthomas Jul 8, 2026
7c5daa8
feat(openfeature): add offline provider example to example apps (FFL-…
btthomas Jul 8, 2026
67447e0
refactor(openfeature): ignore mismatched context in offline provider …
btthomas Jul 17, 2026
b2be3f3
refactor(openfeature): address PR3 review findings (docs, events, emp…
btthomas Jul 17, 2026
c1acba5
fix(openfeature): reject initialize when the loaded offline config is…
btthomas Jul 17, 2026
a5a21e8
chore(openfeature): remove provider_set_configuration plan file
btthomas Jul 17, 2026
f18e2c9
refactor(openfeature): error on context mismatch in offline provider …
btthomas Jul 21, 2026
a3716b4
fix(example): use distinct clientNames for the online and offline pro…
btthomas Jul 21, 2026
da4497e
docs(openfeature): clarify offline context recovery, domains, and compat
btthomas Jul 21, 2026
367d9dc
test(openfeature): strengthen offline provider assertions + domain cases
btthomas Jul 21, 2026
4f4f7f5
chore(openfeature): require web-sdk ^1.8.0; tighten offline docs
btthomas Jul 22, 2026
a316444
test(openfeature): cover the documented domain setup order
btthomas Jul 22, 2026
ffbf10a
Merge pull request #1330 from DataDog/blake.thomas/FFL-2666-PR1
btthomas Jul 22, 2026
10fb8e7
Merge pull request #1331 from DataDog/blake.thomas/FFL-2666-PR2
btthomas Jul 22, 2026
2f3c59e
Merge pull request #1332 from DataDog/blake.thomas/FFL-2666-PR3
btthomas Jul 22, 2026
207ebfa
fix(flags): address code-review findings
btthomas Jul 22, 2026
0a5c18a
docs(flags): clarify that an empty-string targeting key is a real sub…
btthomas Jul 22, 2026
d30d1fd
docs(flags): correct the object-variation decoder comment + test the …
btthomas Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 34 additions & 17 deletions example-new-architecture/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import {
DdFlags,
PropagatorType,
} from '@datadog/mobile-react-native';
import {DatadogOpenFeatureProvider} from '@datadog/mobile-react-native-openfeature';
import {
OpenFeature,
OpenFeatureProvider,
useBooleanFlagDetails,
useObjectFlagDetails,
} from '@openfeature/react-sdk';
import {setFlagsProvider} from './flags/flagsProvider';
import {FlagsSourceToggle} from './flags/FlagsSourceToggle';
import React, {Suspense} from 'react';
import type {PropsWithChildren} from 'react';
import {
Expand Down Expand Up @@ -75,9 +76,10 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
// Enable Datadog Flags feature.
await DdFlags.enable();

// Set the provider with OpenFeature.
const provider = new DatadogOpenFeatureProvider();
OpenFeature.setProvider(provider);
// Set the flags provider. This app defaults to the offline provider (a bundled
// ConfigurationWire, no network); the "Flags source" switch flips to the online provider
// (CDN) at runtime.
await setFlagsProvider('offline');

// Datadog SDK usage examples.
await DdRum.startView('main', 'Main');
Expand All @@ -93,18 +95,10 @@ import {APPLICATION_ID, CLIENT_TOKEN, ENVIRONMENT} from './ddCredentials';
})();

function AppWithProviders() {
React.useEffect(() => {
const user = {
id: 'user-123',
favoriteFruit: 'apple',
};

OpenFeature.setContext({
targetingKey: user.id,
favoriteFruit: user.favoriteFruit,
});
}, []);

// No OpenFeature.setContext here on purpose: the offline precomputed configuration is a
// single-subject snapshot served against the context it was computed for (see the wire's
// embedded context in flags/). Setting a different runtime context would put the provider into
// the OpenFeature ERROR state and fall back to coded defaults.
return (
<Suspense
fallback={
Expand All @@ -123,6 +117,8 @@ function App(): React.JSX.Element {
const greetingFlag = useObjectFlagDetails('rn-sdk-test-json-flag', {
greeting: 'Default greeting',
});
// The boolean flag carried by the bundled offline configuration.
const offlineFlag = useBooleanFlagDetails('rn-sdk-test-boolean-flag', false);

const isDarkMode = useColorScheme() === 'dark';
const backgroundStyle = {
Expand All @@ -138,6 +134,27 @@ function App(): React.JSX.Element {
<ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}>
<Header />

<View style={styles.sectionContainer}>
<Text
style={[
styles.sectionTitle,
{color: isDarkMode ? Colors.white : Colors.black},
]}>
Offline Feature Flag
</Text>
<Text style={styles.sectionDescription}>
{offlineFlag.value
? 'Greetings from the offline Feature Flags!'
: 'Welcome!'}
</Text>
<Text style={styles.sectionDescription}>
`{offlineFlag.flagKey}` ={' '}
<Text style={styles.highlight}>{String(offlineFlag.value)}</Text>,
reason <Text style={styles.highlight}>{offlineFlag.reason}</Text>.
</Text>
<FlagsSourceToggle initialSource="offline" />
</View>

<View style={{backgroundColor: isDarkMode ? Colors.black : Colors.white}}>
<Section title={greetingFlag.value.greeting}>
The title of this section is based on the{' '}
Expand Down
63 changes: 63 additions & 0 deletions example-new-architecture/flags/FlagsSourceToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, {useState} from 'react';
import {
View,
Text,
Switch,
ActivityIndicator,
StyleSheet,
} from 'react-native';

import {setFlagsProvider} from './flagsProvider';
import type {FlagsSource} from './flagsProvider';

/**
* A runtime switch between the offline (bundled, no network) and online (CDN) flags
* providers. Toggling re-sets the OpenFeature provider, which re-renders any `<FeatureFlag>`.
*/
export const FlagsSourceToggle = ({
initialSource = 'offline',
}: {
initialSource?: FlagsSource;
}) => {
const [offline, setOffline] = useState(initialSource === 'offline');
const [busy, setBusy] = useState(false);

const onToggle = async (nextOffline: boolean) => {
setBusy(true);
try {
await setFlagsProvider(nextOffline ? 'offline' : 'online');
setOffline(nextOffline);
} finally {
setBusy(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.label}>
Flags source: {offline ? 'offline (bundled)' : 'online (CDN)'}
</Text>
<Switch
accessibilityLabel="flags_source_toggle"
value={offline}
onValueChange={onToggle}
disabled={busy}
/>
{busy ? <ActivityIndicator style={styles.spinner} /> : null}
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
},
label: {
marginRight: 10,
},
spinner: {
marginLeft: 10,
},
});
40 changes: 40 additions & 0 deletions example-new-architecture/flags/flagsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
DatadogOpenFeatureProvider,
DatadogOfflineOpenFeatureProvider,
configurationFromString,
} from '@datadog/mobile-react-native-openfeature';
import {OpenFeature} from '@openfeature/react-sdk';

import {buildSampleWire} from './sampleOfflineConfiguration';

export type FlagsSource = 'online' | 'offline';

/**
* Select which OpenFeature provider backs flag evaluations, and (re)set it at runtime.
*
* - `offline`: loads a bundled `ConfigurationWire` into `DatadogOfflineOpenFeatureProvider`
* **before** setting it, so flags resolve immediately with no network request.
* - `online`: the standard `DatadogOpenFeatureProvider`, which fetches assignments from the CDN.
*
* The two providers use distinct `clientName`s so each is backed by its own `FlagsClient`.
* Sharing one client across the offline and online modes is unsupported (an online fetch would
* discard the offline configuration).
*
* `DdFlags.enable()` must have been called once before this (it enables the native feature).
*/
export const setFlagsProvider = async (source: FlagsSource): Promise<void> => {
if (source === 'offline') {
const provider = new DatadogOfflineOpenFeatureProvider({
clientName: 'offline',
});
provider.setConfiguration(
configurationFromString(buildSampleWire()),
);
await OpenFeature.setProviderAndWait(provider);
return;
}

await OpenFeature.setProviderAndWait(
new DatadogOpenFeatureProvider({clientName: 'online'}),
);
};
50 changes: 50 additions & 0 deletions example-new-architecture/flags/sampleOfflineConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// The flag key shared with the online example, so the UI is comparable across providers.
export const OFFLINE_FLAG_KEY = 'rn-sdk-test-boolean-flag';

export type OfflineWireContext = {targetingKey?: string} & Record<
string,
string | number | boolean
>;

// The evaluation context the bundled configuration is precomputed for. Because the wire
// carries its own context, the app does not need to call `OpenFeature.setContext` for the
// offline flow.
export const DEFAULT_OFFLINE_CONTEXT: OfflineWireContext = {
targetingKey: 'example-offline-user',
};

/**
* Build a bundled `ConfigurationWire` v1 string for the offline example.
*
* Mirrors the shape the Datadog Flags CDN returns, but is bundled with the app so the demo
* is fully offline — it never hits the network. Flip `variationValue` to `false` to confirm
* the flag's fallback renders.
*/
export const buildSampleWire = (
context: OfflineWireContext = DEFAULT_OFFLINE_CONTEXT,
variationValue = true,
): string =>
JSON.stringify({
version: 1,
precomputed: {
context,
response: JSON.stringify({
data: {
attributes: {
obfuscated: false,
flags: {
[OFFLINE_FLAG_KEY]: {
variationType: 'boolean',
variationValue,
variationKey: String(variationValue),
allocationKey: 'offline-example-alloc',
reason: 'STATIC',
doLog: true,
extraLogging: {},
},
},
},
},
}),
},
});
11 changes: 6 additions & 5 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import style from './screens/styles';
import { navigationRef } from './NavigationRoot';
import { DdRumReactNavigationTracking, NavigationTrackingOptions, ParamsTrackingPredicate, ViewNamePredicate, ViewTrackingPredicate } from '@datadog/mobile-react-navigation';
import { DatadogProvider, TrackingConsent, DdFlags } from '@datadog/mobile-react-native'
import { DatadogOpenFeatureProvider } from '@datadog/mobile-react-native-openfeature';
import { OpenFeature, OpenFeatureProvider } from '@openfeature/react-sdk';
import { OpenFeatureProvider } from '@openfeature/react-sdk';
import { Route } from "@react-navigation/native";
import { NestedNavigator } from './screens/NestedNavigator/NestedNavigator';
import { getDatadogConfig, onDatadogInitialization } from './ddUtils';
import { setFlagsProvider } from './flags/flagsProvider';

const Tab = createBottomTabNavigator();

Expand Down Expand Up @@ -74,9 +74,10 @@ const handleDatadogInitialization = async () => {
// Enable Datadog Flags feature.
await DdFlags.enable();

// Set the provider with OpenFeature.
const provider = new DatadogOpenFeatureProvider();
OpenFeature.setProvider(provider);
// Set the flags provider. This example defaults to the offline provider (a bundled
// ConfigurationWire, no network); the "Flags source" switch on the Home screen flips to
// the online provider (CDN) at runtime.
await setFlagsProvider('offline');
}

export default function App() {
Expand Down
57 changes: 57 additions & 0 deletions example/src/components/FlagsSourceToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useState } from 'react';
import { View, Text, Switch, ActivityIndicator, StyleSheet } from 'react-native';

import { setFlagsProvider } from '../flags/flagsProvider';
import type { FlagsSource } from '../flags/flagsProvider';

/**
* A runtime switch between the offline (bundled, no network) and online (CDN) flags
* providers. Toggling re-sets the OpenFeature provider, which re-renders any `<FeatureFlag>`.
*/
export const FlagsSourceToggle = ({
initialSource = 'offline'
}: {
initialSource?: FlagsSource;
}) => {
const [offline, setOffline] = useState(initialSource === 'offline');
const [busy, setBusy] = useState(false);

const onToggle = async (nextOffline: boolean) => {
setBusy(true);
try {
await setFlagsProvider(nextOffline ? 'offline' : 'online');
setOffline(nextOffline);
} finally {
setBusy(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.label}>
Flags source: {offline ? 'offline (bundled)' : 'online (CDN)'}
</Text>
<Switch
accessibilityLabel="flags_source_toggle"
value={offline}
onValueChange={onToggle}
disabled={busy}
/>
{busy ? <ActivityIndicator style={styles.spinner} /> : null}
</View>
);
};

const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12
},
label: {
marginRight: 10
},
spinner: {
marginLeft: 10
}
});
44 changes: 44 additions & 0 deletions example/src/flags/flagsProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
DatadogOpenFeatureProvider,
DatadogOfflineOpenFeatureProvider,
configurationFromString
} from '@datadog/mobile-react-native-openfeature';
import { OpenFeature } from '@openfeature/react-sdk';

import { buildSampleWire } from './sampleOfflineConfiguration';
import type { OfflineWireContext } from './sampleOfflineConfiguration';

export type FlagsSource = 'online' | 'offline';

/**
* Select which OpenFeature provider backs flag evaluations, and (re)set it at runtime.
*
* - `offline`: loads a bundled `ConfigurationWire` into `DatadogOfflineOpenFeatureProvider`
* **before** setting it, so flags resolve immediately with no network request.
* - `online`: the standard `DatadogOpenFeatureProvider`, which fetches assignments from the CDN.
*
* The two providers use distinct `clientName`s so each is backed by its own `FlagsClient`.
* Sharing one client across the offline and online modes is unsupported (an online fetch would
* discard the offline configuration).
*
* `DdFlags.enable()` must have been called once before this (it enables the native feature).
*/
export const setFlagsProvider = async (
source: FlagsSource,
offlineContext?: OfflineWireContext
): Promise<void> => {
if (source === 'offline') {
const provider = new DatadogOfflineOpenFeatureProvider({
clientName: 'offline'
});
provider.setConfiguration(
configurationFromString(buildSampleWire(offlineContext))
);
await OpenFeature.setProviderAndWait(provider);
return;
}

await OpenFeature.setProviderAndWait(
new DatadogOpenFeatureProvider({ clientName: 'online' })
);
};
Loading