Skip to content

Add hook to transform toggles before updating #249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ The Unleash SDK takes the following options:
| environment | no | `default` | Sets the `environment` option of the [Unleash context](https://docs.getunleash.io/reference/unleash-context). This does **not** affect the SDK's [Unleash environment](https://docs.getunleash.io/reference/environments). |
| usePOSTrequests | no | `false` | Configures the client to use POST requests instead of GET when requesting enabled features. This is helpful when sensitive information (like user email, when used as a user ID) is passed in the context to avoid leaking it in the URL. NOTE: Post requests are not supported by the frontend api built into Unleash. |
| experimental | no | `{}` | Enabling optional experimentation. `togglesStorageTTL` : How long (Time To Live), in seconds, the toggles in storage are considered valid and should not be fetched on start. If set to 0 will disable expiration checking and will be considered always expired. |
| transformToggles | no | `n/a` | Enabling update of the subset of the toggles. Function signature: `transformToggles?: (toggles: IToggle[]) => Promise<IToggle[]>`. Adding the possibility to fetch external data, applying async logic (e.g., user permissions, remote config), and/or dynamically deciding which toggles to keep or modify. |

### Listen for updates via the EventEmitter

Expand Down Expand Up @@ -220,6 +221,27 @@ const unleash = new UnleashClient({
},
});
```

### Update the subset of the toggles
If you need to dynamically modify or filter the list of toggles after they are fetched from the Unleash proxy or the Unleash Edge — but before they’re applied to the client — you can use the optional `transformToggles` hook.
This is especially useful for cases where toggle processing depends on external data, such as user roles, remote config, or custom logic that requires asynchronous operations.

```js
const client = initialize({
url: 'https://app.unleash-hosted.com/demo/proxy',
clientKey: 'proxy-client-key',
appName: 'my-app',
transformToggles: async (toggles) => {
const allowedToggleNames = await fetchUserTogglePermissions();
return toggles.filter(toggle => allowedToggleNames.includes(toggle.name));
},
});
```
When to use:
- You need to filter toggles based on async context (e.g., user permissions);
- You want to enrich toggle data from another service before applying;
- You want to limit the updates to only relevant features.

## How to use in node.js

This SDK can also be used in node.js applications (from v1.4.0). Please note that you will need to provide a valid "fetch" implementation. Only ECMAScript modules is exported from this package.
Expand Down
10 changes: 8 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ interface IConfig extends IStaticContext {
impressionDataAll?: boolean;
usePOSTrequests?: boolean;
experimental?: IExperimentalConfig;
transformToggles?: (toggles: IToggle[]) => Promise<IToggle[]>;
}

interface IExperimentalConfig {
Expand Down Expand Up @@ -171,6 +172,7 @@ export class UnleashClient extends TinyEmitter {
private experimental: IExperimentalConfig;
private lastRefreshTimestamp: number;
private connectionId: string;
private transformToggles?: (toggles: IToggle[]) => Promise<IToggle[]>;

constructor({
storageProvider,
Expand All @@ -193,6 +195,7 @@ export class UnleashClient extends TinyEmitter {
impressionDataAll = false,
usePOSTrequests = false,
experimental,
transformToggles,
}: IConfig) {
super();
// Validations
Expand Down Expand Up @@ -262,8 +265,8 @@ export class UnleashClient extends TinyEmitter {
this.bootstrap =
bootstrap && bootstrap.length > 0 ? bootstrap : undefined;
this.bootstrapOverride = bootstrapOverride;

this.connectionId = uuidv4();
this.transformToggles = transformToggles;

this.metrics = new Metrics({
onError: this.emit.bind(this, EVENTS.ERROR),
Expand Down Expand Up @@ -579,7 +582,10 @@ export class UnleashClient extends TinyEmitter {
if (response.ok) {
this.etag = response.headers.get('ETag') || '';
const data = await response.json();
await this.storeToggles(data.toggles);
const transformedToggles = this.transformToggles
? await this.transformToggles(data.toggles)
: data.toggles;
await this.storeToggles(transformedToggles);

if (this.sdkState !== 'healthy') {
this.sdkState = 'healthy';
Expand Down
Loading