Skip to content

Commit

Permalink
feat: remove context field utility (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew authored Feb 5, 2024
1 parent abfd7dd commit 5ab4eab
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 18 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ if (variant.name === 'blue') {

#### Updating the Unleash context

The [Unleash context](https://docs.getunleash.io/reference/unleash-context) is used to evaluate features against attributes of a the current user. To update and configure the Unleash context in this SDK, use the `updateContext` and `setContextField` methods.
The [Unleash context](https://docs.getunleash.io/reference/unleash-context) is used to evaluate features against attributes of a the current user. To update and configure the Unleash context in this SDK, use the `updateContext`, `setContextField` and `removeContextField` methods.

The context you set in your app will be passed along to the Unleash proxy or the front-end API as query parameters for feature evaluation.

Expand All @@ -103,6 +103,14 @@ unleash.updateContext({ userId: '1233' });
unleash.setContextField('userId', '4141');
```

The `removeContextField` method only acts on the property that you choose. It does not affect any other properties of the Unleash context.

```js
unleash.updateContext({ userId: '1233', sessionId: 'sessionNotAffected' });

unleash.removeContextField('userId');
```

### Available options

The Unleash SDK takes the following options:
Expand Down
23 changes: 23 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,29 @@ test('Should setContextField with userId', async () => {
expect(context.userId).toBe(userId);
});

test('Should removeContextField', async () => {
const userId = 'some-id-123';
const customValue = 'customValue';
const config: IConfig = {
url: 'http://localhost/test',
clientKey: '12',
appName: 'web',
};
const client = new UnleashClient(config);
client.setContextField('userId', userId);
client.setContextField('customField', customValue);

client.removeContextField('userId');
client.removeContextField('customField');
const context = client.getContext();

expect(context).toEqual({
appName: 'web',
environment: 'default',
properties: {},
});
});

test('Should setContextField with sessionId', async () => {
const sessionId = 'some-session-id-123';
const config: IConfig = {
Expand Down
57 changes: 40 additions & 17 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ import LocalStorageProvider from './storage-provider-local';
import EventsHandler from './events-handler';
import { notNullOrUndefined, urlWithContextAsQuery } from './util';

const DEFINED_FIELDS = ['userId', 'sessionId', 'remoteAddress', 'currentTime'];
const DEFINED_FIELDS = [
'userId',
'sessionId',
'remoteAddress',
'currentTime',
] as const;
type DefinedField = (typeof DEFINED_FIELDS)[number];

interface IStaticContext {
appName: string;
Expand All @@ -25,6 +31,10 @@ interface IMutableContext {

type IContext = IStaticContext & IMutableContext;

const isDefinedContextField = (field: string): field is DefinedField => {
return DEFINED_FIELDS.includes(field as DefinedField);
};

interface IConfig extends IStaticContext {
url: URL | string;
clientKey: string;
Expand Down Expand Up @@ -271,6 +281,22 @@ export class UnleashClient extends TinyEmitter {
return { ...variant, feature_enabled: enabled };
}

private async updateToggles() {
if (this.timerRef || this.readyEventEmitted) {
await this.fetchToggles();
} else if (this.started) {
await new Promise<void>((resolve) => {
const listener = () => {
this.fetchToggles().then(() => {
this.off(EVENTS.READY, listener);
resolve();
});
};
this.once(EVENTS.READY, listener);
});
}
}

public async updateContext(context: IMutableContext): Promise<void> {
// @ts-expect-error Give the user a nicer error message when
// including static fields in the mutable context object
Expand All @@ -286,35 +312,32 @@ export class UnleashClient extends TinyEmitter {
};
this.context = { ...staticContext, ...context };

if (this.timerRef || this.readyEventEmitted) {
await this.fetchToggles();
} else if (this.started) {
await new Promise<void>((resolve) => {
const listener = () => {
this.fetchToggles().then(() => {
this.off(EVENTS.READY, listener);
resolve();
});
};
this.once(EVENTS.READY, listener);
});
}
await this.updateToggles();
}

public getContext() {
return { ...this.context };
}

public setContextField(field: string, value: string) {
if (DEFINED_FIELDS.includes(field)) {
if (isDefinedContextField(field)) {
this.context = { ...this.context, [field]: value };
} else {
const properties = { ...this.context.properties, [field]: value };
this.context = { ...this.context, properties };
}
if (this.timerRef) {
this.fetchToggles();

this.updateToggles();
}

public removeContextField(field: string): void {
if (isDefinedContextField(field)) {
this.context = { ...this.context, [field]: undefined };
} else if (typeof this.context.properties === 'object') {
delete this.context.properties[field];
}

this.updateToggles();
}

private async init(): Promise<void> {
Expand Down

0 comments on commit 5ab4eab

Please sign in to comment.