OpenFeature Web Provider
Introduction
The OpenFeature Web Provider is intended to be used in combination with the OpenFeature Web SDK. All modern web browsers on popular platforms should be supported.
The ConfigDirector OpenFeature Web Provider is tested on the latest versions of Chrome, Firefox, Safari, and Edge.
Telemetry collection within browser SDKs uses Web Workers to aggregate and send telemetry data. If the provider runs on older browsers without Web Workers support, config evaluation will continue to work but no telemetry data will be collected for that session.
Installation
The provider can be installed from NPM: https://www.npmjs.com/package/@configdirector/openfeature-web-provider
npm install --save @openfeature/web-sdk @configdirector/openfeature-web-provider
yarn add @openfeature/web-sdk @openfeature/core @configdirector/openfeature-web-provider
pnpm add @openfeature/web-sdk @openfeature/core @configdirector/openfeature-web-provider
bun add @openfeature/web-sdk @openfeature/core @configdirector/openfeature-web-provider
Configure and initialize the client
- Create an instance of the provider using your client SDK key. You can retrieve a client SDK key for each environment under
SDK keysin the dashboard's navigation panel. - Set the OpenFeature provider, together with the context you already know, so the first connection carries it.
- Get a client instance from OpenFeature.
import { OpenFeature } from "@openfeature/web-sdk";
import { ConfigDirectorProvider } from "@configdirector/openfeature-web-provider";
const provider = new ConfigDirectorProvider("YOUR-CLIENT-SDK-KEY");
try {
await OpenFeature.setProviderAndWait(provider, {
targetingKey: "12345", // In OpenFeature, the `targetingKey` represents the context's identifier
name: "Example User",
traits: {
region: "North America", // Any arbitrary traits which can be referenced in targeting rules
},
});
} catch (error) {
console.warn("Continuing until ConfigDirector sends config:", error);
}
const client = OpenFeature.getClient();
The provider connects with the context it has when it is registered. Pass the context you already know to setProviderAndWait, or set it with OpenFeature.setContext before registering the provider, so the first connection carries it.
Additional configuration options
Additional configuration options can be passed into the provider in the optional second argument of the constructor.
For example, the metadata can be provided like this:
import { ConfigDirectorProvider } from "@configdirector/openfeature-web-provider";
const provider = new ConfigDirectorProvider("YOUR-CLIENT-SDK-KEY", {
metadata: {
appName: "YOUR-APP-NAME",
appVersion: "1.0.2",
},
});
The provider accepts the same configuration options as the Javascript SDK client, refer to the additional configuration options section of the Javascript SDK for a full list.
Values kept on the device
By default, the provider keeps the last values it received from ConfigDirector in the browser's localStorage, and serves them on the next launch of the same context until live values arrive. An application opened without a network, after at least one successful connection, resolves flags to the values ConfigDirector last sent instead of the default values passed to the OpenFeature client.
Registering the provider still waits for live values or the connection timeout: setProviderAndWait rejects with ProviderNotReadyError on a timeout as it does without kept values, and the provider then reports the STALE status, with a Stale event, while it serves the kept values. Flags resolve to the kept values in that status. Once live values arrive, the provider reports READY with a Ready event and the kept values are replaced. A context change that cannot connect reports an error and then STALE again, and keeps serving the current values. Flags resolved from the kept values carry the CACHED reason; see Evaluation details and hooks.
The values are kept for the context they were evaluated for, and served on the next launch only when the evaluation context has the same kind and targetingKey. A context without a targetingKey matches on the same site, as long as the site's data has not been cleared, because ConfigDirector derives its identifier from the instance ID the SDK stores; such a context keeps its percentage rollout bucket for as long as that instance ID lasts. Other attributes take no part in the match: live values correct them within a second when there is a network. A context change keeps serving the current values until the new context's values arrive, as it does with live values.
Turn it off, or supply your own store, with the caching option, which takes the same object the JavaScript SDK takes: enabled, defaulting to true, and provider, a CachingProvider with read(), write(value), remove() and sizeLimit() in bytes, defaulting to localStorage under a key derived from the client SDK key with a size limit of 256 KB. The entry is one string whose format belongs to the SDK and may change between SDK versions. An entry above the size limit is removed with one warning, a provider that fails is logged once and never stops the provider, and nothing is kept in offline mode. The entry, and the localStorage key the provider uses by default, carry a SHA-256 digest of the client SDK key, never the key itself. Browsers compute that digest only on pages served over https or from localhost, so on a plain http page on another host nothing is kept and a provider of your own is not called.
import { ConfigDirectorProvider } from "@configdirector/openfeature-web-provider";
const provider = new ConfigDirectorProvider("YOUR-CLIENT-SDK-KEY", {
caching: { enabled: false },
});
Retrieve config values
To retrieve config values, use the OpenFeature client:
const booleanValue = client.getBooleanValue("my-config-key", false);
const stringValue = client.getStringValue("my-string-config-key", "Default");
For additional information regarding the OpenFeature client refer to the OpenFeature Web SDK documentation.
Evaluation details and hooks
The detailed getters, such as getBooleanDetails, say why a value was returned:
| Outcome | Reason | Error code | Variant |
|---|---|---|---|
| A value was found | TARGETING_MATCH | The value ID | |
| A value was served from the values kept on the device | CACHED | The value ID | |
| The config carries no value | DEFAULT | ||
| The config key is unknown | ERROR | FLAG_NOT_FOUND | |
| No config values have arrived yet | ERROR | PROVIDER_NOT_READY | |
| The value cannot be read as the requested type | ERROR | TYPE_MISMATCH |
The variant is ConfigDirector's ID for the value served, a stable hash you can send to analytics in place of the value itself; values served in offline mode have none. In the three error cases the getters return the in-code default value you pass, and OpenFeature runs the error stage of your hooks, so an error-monitoring hook sees a missing config or a value of another type. getObjectValue with an object as its in-code default value reads only a JSON object, and with an array only a JSON array; JSON of the other shape gives TYPE_MISMATCH.
Before version 1.10.0 of the provider, the details carried no reason, variant or error code, and error hooks did not run.
Update the context
The provider connects with the context it has when it is registered, as shown in Configure and initialize the client. To change the context afterwards, for example when a user signs in, call OpenFeature.setContext. Each change opens a new connection with the new context:
await OpenFeature.setContext({
targetingKey: "654321",
name: "Another User",
traits: {
region: "Australia",
},
});
To evaluate flags for an entity other than a user, such as a tenant, set the context kind with a top-level kind attribute:
await OpenFeature.setContext({
targetingKey: "acme",
kind: "tenant",
name: "Acme Inc.",
traits: {
plan: "enterprise",
},
});
Leaving kind out means user. A kind inside traits is an ordinary trait, not the context kind. A context kind is 1 to 64 lowercase letters, digits, dashes, or underscores. A context with an invalid context kind is ignored: the SDK logs an error, and flags still resolve successfully, as if no context had been given. Context kinds are available from version 1.7.0 of the provider.
targetingKey will map to ConfigDirector's context id, and its kind to the context kind.Register the provider with the context you know when the page loads, so the first connection carries it. A context set only afterwards replaces a first connection that had none, and that first connection counts as a separate monthly active user.For additional information regarding the OpenFeature client refer to the OpenFeature Web SDK documentation.
Test your code
Code that reads flags through OpenFeature is tested by swapping the provider, so it never has to change for a test and nothing from ConfigDirector is involved. The OpenFeature Web SDK ships an in-memory provider for that: TypedInMemoryProvider, in @openfeature/web-sdk 1.8 and newer, which serves the values your test sets. Earlier versions ship the same provider as InMemoryProvider, now deprecated.
Each flag is a set of named variants and the defaultVariant it resolves to:
import { OpenFeature, TypedInMemoryProvider } from "@openfeature/web-sdk";
const provider = new TypedInMemoryProvider({
"new-checkout": { variants: { on: true, off: false }, defaultVariant: "on" },
"max-items": { variants: { default: 20 }, defaultVariant: "default" },
});
afterEach(() => OpenFeature.close());
test("shows the new checkout", async () => {
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();
expect(client.getBooleanValue("new-checkout", false)).toBe(true);
expect(client.getNumberValue("max-items", 0)).toBe(20);
await provider.putConfiguration({
"new-checkout": { variants: { on: true, off: false }, defaultVariant: "off" },
});
expect(client.getBooleanValue("new-checkout", true)).toBe(false);
});
putConfiguration replaces every flag and emits ConfigurationChanged with the keys it was given, so a component that re-reads its flags on that event follows the change. A flag can also carry a contextEvaluator, a function from the evaluation context to the name of the variant to serve, for a test of code that targets specific users. OpenFeature is one global, so shut it down after each test with OpenFeature.close(), which lets the next test register its own provider.
A React application that reads flags through @openfeature/react-sdk has OpenFeatureTestProvider from that package as well, a component that serves the values it is given to the hooks beneath it.
Overview
Use ConfigDirector through the vendor-neutral OpenFeature API. The providers for the web, Swift, Android, Flutter, Node.js, Python, Java, .NET, and Go, how the evaluation context and the getters map onto ConfigDirector, and what to expect from initialization, events, and tests.
Swift
ConfigDirector Provider for the OpenFeature Swift SDK