Skip to content
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

feat: add fetch options with flag keys option #23

Merged
merged 1 commit into from
Jul 19, 2023
Merged
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
1 change: 1 addition & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export {
RemoteEvaluationDefaults,
} from './types/config';
export { Experiment } from './factory';
export { FetchOptions } from './types/fetch';
export { ExperimentUser } from './types/user';
export { Variant, Variants } from './types/variant';
export { LocalEvaluationClient } from './local/client';
Expand Down
35 changes: 28 additions & 7 deletions packages/node/src/remote/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
RemoteEvaluationDefaults,
RemoteEvaluationConfig,
} from '../types/config';
import { FetchOptions } from '../types/fetch';
import { HttpClient } from '../types/transport';
import { ExperimentUser } from '../types/user';
import { Variant, Variants } from '../types/variant';
Expand Down Expand Up @@ -37,32 +38,39 @@ export class RemoteEvaluationClient {
* This method will automatically retry if configured (default).
*
* @param user The {@link ExperimentUser} context
* @param options The {@link FetchOptions} for this specific fetch request.
* @return The {@link Variants} for the user on success, empty
* {@link Variants} on error.
*/
public async fetch(user: ExperimentUser): Promise<Variants> {
public async fetch(
user: ExperimentUser,
options?: FetchOptions,
): Promise<Variants> {
if (!this.apiKey) {
throw Error('Experiment API key is empty');
}
try {
return await this.fetchInternal(user);
return await this.fetchInternal(user, options);
} catch (e) {
console.error('[Experiment] Failed to fetch variants: ', e);
return {};
}
}

private async fetchInternal(user: ExperimentUser): Promise<Variants> {
private async fetchInternal(
user: ExperimentUser,
options?: FetchOptions,
): Promise<Variants> {
if (!this.apiKey) {
throw Error('Experiment API key is empty');
}
this.debug('[Experiment] Fetching variants for user: ', user);
try {
return await this.doFetch(user, this.config.fetchTimeoutMillis);
return await this.doFetch(user, this.config.fetchTimeoutMillis, options);
} catch (e) {
console.error('[Experiment] Fetch failed: ', e);
try {
return await this.retryFetch(user);
return await this.retryFetch(user, options);
} catch (e) {
console.error(e);
}
Expand All @@ -73,6 +81,7 @@ export class RemoteEvaluationClient {
private async doFetch(
user: ExperimentUser,
timeoutMillis: number,
options?: FetchOptions,
): Promise<Variants> {
const userContext = this.addContext(user || {});
const endpoint = `${this.config.serverUrl}/sdk/vardata`;
Expand All @@ -83,6 +92,11 @@ export class RemoteEvaluationClient {
Authorization: `Api-Key ${this.apiKey}`,
'X-Amp-Exp-User': encodedUser,
};
if (options && options.flagKeys) {
headers['X-Amp-Exp-Flag-Keys'] = Buffer.from(
JSON.stringify(options.flagKeys),
).toString('base64url');
}
this.debug('[Experiment] Fetch variants for user: ', userContext);
const response = await this.httpClient.request(
endpoint,
Expand All @@ -102,7 +116,10 @@ export class RemoteEvaluationClient {
return variants;
}

private async retryFetch(user: ExperimentUser): Promise<Variants> {
private async retryFetch(
user: ExperimentUser,
options?: FetchOptions,
): Promise<Variants> {
if (this.config.fetchRetries == 0) {
return {};
}
Expand All @@ -112,7 +129,11 @@ export class RemoteEvaluationClient {
for (let i = 0; i < this.config.fetchRetries; i++) {
await sleep(delayMillis);
try {
return await this.doFetch(user, this.config.fetchRetryTimeoutMillis);
return await this.doFetch(
user,
this.config.fetchRetryTimeoutMillis,
options,
);
} catch (e) {
console.error('[Experiment] Retry falied: ', e);
err = e;
Expand Down
9 changes: 9 additions & 0 deletions packages/node/src/types/fetch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Options to modify the behavior of a remote evaluation fetch request.
*/
export type FetchOptions = {
/**
* Specific flag keys to evaluate and set variants for.
*/
flagKeys?: string[];
};
8 changes: 8 additions & 0 deletions packages/node/test/remote/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ test('ExperimentClient.fetch, retry once, timeout first then succeed with 0 back
const variant = variants['sdk-ci-test'];
expect(variant).toEqual({ value: 'on', payload: 'payload' });
});

test('ExperimentClient.fetch, with flag keys options, success', async () => {
const client = new RemoteEvaluationClient(API_KEY, {});
const variants = await client.fetch(testUser, { flagKeys: ['sdk-ci-test'] });
expect(variants).toEqual({
'sdk-ci-test': { value: 'on', payload: 'payload' },
});
});