From 9f72fe38df0b032faf94102b22562ee00db45af3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 10 Jul 2025 15:33:35 -0300 Subject: [PATCH 1/4] Update README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rodrigo López Dato --- libs/providers/flagsmith-client/README.md | 93 +++++++++++++---------- 1 file changed, 54 insertions(+), 39 deletions(-) diff --git a/libs/providers/flagsmith-client/README.md b/libs/providers/flagsmith-client/README.md index 42bcb69ba..494826422 100644 --- a/libs/providers/flagsmith-client/README.md +++ b/libs/providers/flagsmith-client/README.md @@ -1,6 +1,6 @@ -# Flagsmith Provider +# Flagsmith OpenFeature provider for client-side JavaScript -This provider is an implementation for the [JavaScript SDK](https://docs.flagsmith.com/clients/javascript/) of [Flagsmith](https://flagsmith.com). +[Flagsmith](https://flagsmith.com) is an open-source feature flagging and remote configuration service. This provider implements the [Flagsmith JavaScript SDK](https://flagsmith.com/docs/clients/javascript/) for client-side applications. ## Installation @@ -8,92 +8,107 @@ This provider is an implementation for the [JavaScript SDK](https://docs.flagsmi npm install @openfeature/flagsmith-client-provider ``` -## Initialising the provider +## Initializing the provider -The Flagsmith Provider can be created with the standard [initialization options](https://docs.flagsmith.com/clients/javascript/#example-initialising-the-sdk) and an optional Flagsmith instance to use. +The Flagsmith OpenFeature provider can be created with the same [initialization options as the Flagsmith SDK](https://docs.flagsmith.com/clients/javascript/#initialisation-options). ```javascript import { FlagsmithClientProvider } from '@openfeature/flagsmith-client-provider'; import { OpenFeature } from '@openfeature/web-sdk'; const flagsmithClientProvider = new FlagsmithClientProvider({ - environmentID: '' + environmentID: 'your_client_side_environment_key', + cacheFlags: true, + cacheOptions: { + skipAPI: true + } }); -OpenFeature.setProvider(flagsmithClientProvider); // Attach the provider to OpenFeature +OpenFeature.setProvider(flagsmithClientProvider); ``` -## Usage with React Native, SSR or custom instances +## Examples + +See our [examples repository](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/open-feature) for usage with various frameworks. + +## Usage with React Native -The Flagsmith Provider can be constructed with a custom Flagsmith instance and optional server-side generated state, [initialization options](https://docs.flagsmith.com/clients/javascript/#example-initialising-the-sdk). +To use the React Native implementation of OpenFeature, install `react-native-flagsmith`: -Note: In order to use the React Native implementation of OpenFeature you will need to install both Flagsmith and react-native-flagsmith. +``` +npm install flagsmith react-native-flagsmith +``` + +Then, pass the `flagsmith` instance from `react-native-flagsmith` when initializing the provider: ```javascript -import flagsmith from 'react-native-flagsmith' // Could also be flagsmith/isomorphic, flagsmith-es or createFlagsmithInstance() +import flagsmith from 'react-native-flagsmith'; import { FlagsmithClientProvider } from '@openfeature/flagsmith-client-provider'; import { OpenFeature } from '@openfeature/web-sdk'; const flagsmithClientProvider = new FlagsmithClientProvider({ - environmentID: '', + environmentID: 'your_client_side_environment_key', flagsmithInstance: flagsmith, - state: serverState, }); -OpenFeature.setProvider(flagsmithClientProvider); // Attach the provider to OpenFeature +OpenFeature.setProvider(flagsmithClientProvider); ``` -## Identifying and setting Traits +See the [React Native example application](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/open-feature/reactnative) for more details. + +## Flag targeting and dynamic evaluation -In Flagsmith, users are [identified](https://docs.flagsmith.com/clients/javascript/#identifying-users) in order to allow for segmentation and percentage rollouts. +In Flagsmith, users can be [identified](https://docs.flagsmith.com/clients/javascript/#identifying-users) to perform targeted flag rollouts. +Traits are key-value pairs that can be used for [segment-based](https://docs.flagsmith.com/basic-features/segments) targeting. -To identify and set traits you can specify a targetingKey(identity) and optionally a set of traits. This will do the equivalent of ``flagsmith.identify(id, traits)`` or pass these to ``flagsmith.init`` if you are calling this before ``OpenFeature.setProvider``. +Flagsmith identifiers and traits make up the [OpenFeature evaluation context](https://openfeature.dev/specification/glossary/#evaluation-context). +They correspond to OpenFeature [targeting keys](https://openfeature.dev/docs/reference/concepts/evaluation-context/#targeting-key) and context attributes respectively: ```javascript -const flagsmithClientProvider = new FlagsmithClientProvider({ - environmentID: '', -}); await OpenFeature.setContext({ targetingKey: 'my-identity-id', traits: { myTraitKey: 'my-trait-value', }, }); -OpenFeature.setProvider(flagsmithClientProvider); // Attach the provider to OpenFeature ``` -To reset the identity you can simply reset the context. This will do the equivalent of ``flagsmith.logout()`` +To reset the identity, set the context to an empty object: ```javascript -await OpenFeature.setContext({ }); +await OpenFeature.setContext({}); ``` -## Resolution reasoning +## Resolution reasons -In Flagsmith, features are evaluated based on the following [Resolution reasons](https://openfeature.dev/specification/types/#resolution-details): +This provider supports the following [resolution reasons](https://openfeature.dev/specification/types/#resolution-reason): ```typescript -StandardResolutionReasons.CACHED | StandardResolutionReasons.STATIC | StandardResolutionReasons.DEFAULT | StandardResolutionReasons.ERROR -``` - -Note that resolutions of type SPLIT may be the result of targetted matching or percentage split however Flagsmith does not expose this information to client-side SDKs. +import { StandardResolutionReasons } from '@openfeature/web-sdk'; +type FlagsmithResolutionReasons = + | typeof StandardResolutionReasons.STATIC + | typeof StandardResolutionReasons.CACHED + | typeof StandardResolutionReasons.DEFAULT + | typeof StandardResolutionReasons.ERROR; +``` ## Events -The Flagsmith provider emits the -following [OpenFeature events](https://openfeature.dev/specification/types#provider-events): +This provider emits the following [events](https://openfeature.dev/specification/types#provider-events): -- PROVIDER_READY -- PROVIDER_ERROR -- PROVIDER_CONFIGURATION_CHANGED +```typescript +import { ProviderEvents } from '@openfeature/web-sdk'; + +type FlagsmithProviderEvents = + | typeof ProviderEvents.Ready + | typeof ProviderEvents.Stale + | typeof ProviderEvents.ConfigurationChanged + | typeof ProviderEvents.Error; +``` ## Building -Run `nx package providers-flagsmith` to build the library. +Run `nx package providers-flagsmith-client` to build the library. ## Running unit tests -Run `nx test providers-flagsmith` to execute the unit tests via [Jest](https://jestjs.io). - -## Examples - -You can find examples using this provider in several frameworks [Here](https://github.com/Flagsmith/flagsmith-js-examples/tree/main/open-feature). +Run `nx test providers-flagsmith-client` to execute the unit tests via [Jest](https://jestjs.io). From dd18406c844dc4d1aaf28d98bb2a4342a1499903 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Thu, 10 Jul 2025 17:13:50 -0300 Subject: [PATCH 2/4] fix(flagsmith): Fetch identity flags when context changes (WIP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rodrigo López Dato --- .../src/lib/flagsmith-client-provider.spec.ts | 24 +++++++++++++++++++ .../src/lib/flagsmith-client-provider.ts | 9 ++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.spec.ts b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.spec.ts index fd4659724..3deea4543 100644 --- a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.spec.ts +++ b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.spec.ts @@ -353,6 +353,30 @@ describe('FlagsmithProvider', () => { ); expect(config.fetch).toHaveBeenCalledTimes(3); }); + + it('should set new traits when the context is updated', async () => { + const config = defaultConfig(); + const provider = new FlagsmithClientProvider({ + logger, + ...config, + }); + await OpenFeature.setProviderAndWait(provider); + await OpenFeature.setContext({ targetingKey: 'first', traitA: 'a' }); + expect(config.fetch).toHaveBeenCalledWith( + `${provider.flagsmithClient.getState().api}identities/`, + expect.objectContaining({ + body: JSON.stringify({ identifier: 'first', traits: [{ trait_key: 'traitA', trait_value: 'a' }] }), + }), + ); + await OpenFeature.setContext({ targetingKey: 'second', traitB: 'b' }); + expect(config.fetch).toHaveBeenCalledWith( + `${provider.flagsmithClient.getState().api}identities/`, + expect.objectContaining({ + body: JSON.stringify({ identifier: 'second', traits: [{ trait_key: 'traitB', trait_value: 'b' }] }), + }), + ); + }); + it('should initialize with the targeting key and traits when passed to initialize', async () => { const targetingKey = 'test'; const traits = { foo: 'bar', example: 123 }; diff --git a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts index 5545f0e6f..113930fd1 100644 --- a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts +++ b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts @@ -53,7 +53,14 @@ export class FlagsmithClientProvider implements Provider { ...(context || {}), }); this.events.emit(ProviderEvents.Stale, { message: 'context has changed' }); - return isLogout ? this._client.logout() : this._client.getFlags(); + if (isLogout) { + return this._client.logout(); + } + if (identity) { + const { targetingKey, ...traits } = context; + return this._client.identify(identity, traits as any); + } + return this._client.getFlags(); } const serverState = this._config.state; From 8ec4423b8b288b7d99bc083805c1edf2ecb8c27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Fri, 11 Jul 2025 09:50:43 -0300 Subject: [PATCH 3/4] Map OpenFeature context to Flagsmith traits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rodrigo López Dato Co-authored-by: Zaimwa9 --- .../src/lib/flagsmith-client-provider.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts index 113930fd1..cfa8bec35 100644 --- a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts +++ b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts @@ -56,9 +56,18 @@ export class FlagsmithClientProvider implements Provider { if (isLogout) { return this._client.logout(); } - if (identity) { - const { targetingKey, ...traits } = context; - return this._client.identify(identity, traits as any); + if (context?.targetingKey) { + const { targetingKey, ...contextTraits } = context; + // OpenFeature context attributes can be Date objects, but Flagsmith traits can't + const traits: Parameters[1] = {}; + for (const [key, value] of Object.entries(contextTraits)) { + if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + traits[key] = value; + } else if (value instanceof Date) { + traits[key] = value.toISOString(); + } + } + return this._client.identify(targetingKey, traits); } return this._client.getFlags(); } From 453c70cf829cfee02720e73d78736ddc89d82ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rodrigo=20L=C3=B3pez=20Dato?= Date: Fri, 11 Jul 2025 10:03:04 -0300 Subject: [PATCH 4/4] comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rodrigo López Dato --- .../flagsmith-client/src/lib/flagsmith-client-provider.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts index 79b52fbf4..f07d5f125 100644 --- a/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts +++ b/libs/providers/flagsmith-client/src/lib/flagsmith-client-provider.ts @@ -58,6 +58,7 @@ export class FlagsmithClientProvider implements Provider { if (context?.targetingKey) { const { targetingKey, ...contextTraits } = context; // OpenFeature context attributes can be Date objects, but Flagsmith traits can't + // https://github.com/Flagsmith/flagsmith-js-client/issues/329 const traits: Parameters[1] = {}; for (const [key, value] of Object.entries(contextTraits)) { if (value === null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {