Skip to content
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
2 changes: 1 addition & 1 deletion __mocks__/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage);
/*
* Mock navigator to avoid undefined access in analytics-connector
*/
global['navigator'] = { product: 'ReactNative' };
global['navigator'] = { product: 'ReactNative' } as unknown as Navigator;

/*
* Mock Native Module
Expand Down
60 changes: 60 additions & 0 deletions __tests__/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { FetchOptions } from '../src/types/client';
import { ExposureTrackingProvider } from '../src/types/exposure';
import { Source } from '../src/types/source';
import { Storage } from '../src/types/storage';
import { HttpClient, SimpleResponse } from '../src/types/transport';
import { ExperimentUser, ExperimentUserProvider } from '../src/types/user';
import { Variant, Variants } from '../src/types/variant';
Expand Down Expand Up @@ -266,7 +267,7 @@
for (let i = 0; i < 10; i++) {
client.variant(serverKey);
}
const variant = client.variant(serverKey);

Check warning on line 270 in __tests__/client.test.ts

View workflow job for this annotation

GitHub Actions / lint

'variant' is assigned a value but never used

expect(trackSpy).toBeCalledTimes(1);
expect(trackSpy).toHaveBeenCalledWith(
Expand Down Expand Up @@ -1012,6 +1013,65 @@
expect(variant.key).toEqual('on');
expect(variant2.key).toEqual('on');
});

test('start with custom storage', async () => {
// Create a custom storage object
const storageObject = {};
const storage = {
get: async (key: string) => storageObject[key],
put: async (key: string, value: string) => {
storageObject[key] = value;
},
delete: async (key: string) => {
delete storageObject[key];
},
} as Storage;

// Create a client with the custom storage object
const client = new ExperimentClient(API_KEY, { storage });
await client.start({ device_id: 'test_device' });

// Check that the flags are stored in the storage object
const storageKey = `amp-exp-$default_instance-${API_KEY.substring(
API_KEY.length - 6,
)}`;
expect(
JSON.parse(storageObject[storageKey + '-flags'])[serverKey],
).toMatchObject({
key: serverKey,
});
// Check that the variant is stored in the storage object
expect(JSON.parse(storageObject[storageKey])[serverKey]).toMatchObject({
key: 'off',
});
});
});

test('fetch with custom storage', async () => {
// Create a custom storage object
const storageObject = {};
const storage = {
get: async (key: string) => storageObject[key],
put: async (key: string, value: string) => {
storageObject[key] = value;
},
delete: async (key: string) => {
delete storageObject[key];
},
} as Storage;

// Create a client with the custom storage object
const client = new ExperimentClient(API_KEY, { storage });
await client.fetch(testUser);

// Check that the variant is stored in the storage object
const storageKey = `amp-exp-$default_instance-${API_KEY.substring(
API_KEY.length - 6,
)}`;
expect(JSON.parse(storageObject[storageKey])[serverKey]).toMatchObject({
key: 'on',
value: 'on',
});
});

describe('fetch retry with different response codes', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/experimentClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export class ExperimentClient implements Client {
httpClient,
);
// Storage & Caching
const storage = new LocalStorage();
const storage = this.config.storage || new LocalStorage();
this.variants = getVariantStorage(
this.apiKey,
this.config.instanceName,
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export { Source } from './types/source';
export * from './types/user';
export * from './types/variant';
export * from './types/exposure';
export * from './types/storage';
export { Logger, LogLevel } from './types/logger';
export { ConsoleLogger } from './logger/consoleLogger';
6 changes: 2 additions & 4 deletions src/storage/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ import { EvaluationFlag, GetVariantsOptions } from '@amplitude/experiment-core';
import { Storage } from '../types/storage';
import { Variant } from '../types/variant';

import { LocalStorage } from './local-storage';

export const getVariantStorage = (
deploymentKey: string,
instanceName: string,
Expand All @@ -22,7 +20,7 @@ export const getVariantStorage = (
export const getFlagStorage = (
deploymentKey: string,
instanceName: string,
storage: Storage = new LocalStorage(),
storage: Storage,
): LoadStoreCache<EvaluationFlag> => {
const truncatedDeployment = deploymentKey.substring(deploymentKey.length - 6);
const namespace = `amp-exp-${instanceName}-${truncatedDeployment}-flags`;
Expand All @@ -32,7 +30,7 @@ export const getFlagStorage = (
export const getVariantsOptionsStorage = (
deploymentKey: string,
instanceName: string,
storage: Storage = new LocalStorage(),
storage: Storage,
): SingleValueStoreCache<GetVariantsOptions> => {
const truncatedDeployment = deploymentKey.substring(deploymentKey.length - 6);
const namespace = `amp-exp-${instanceName}-${truncatedDeployment}-variants-options`;
Expand Down
2 changes: 1 addition & 1 deletion src/storage/local-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
import { Storage } from '../types/storage';

export class LocalStorage implements Storage {
async get(key: string): Promise<string> {
async get(key: string): Promise<string | null> {
return await AsyncStorage.getItem(key);
}

Expand Down
8 changes: 8 additions & 0 deletions src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { FetchHttpClient } from '../transport/http';
import { ExposureTrackingProvider } from './exposure';
import { Logger, LogLevel } from './logger';
import { Source } from './source';
import { Storage } from './storage';
import { HttpClient } from './transport';
import { ExperimentUserProvider } from './user';
import { Variant, Variants } from './variant';
Expand Down Expand Up @@ -146,6 +147,12 @@ export interface ExperimentConfig {
* (Advanced) Use your own http client.
*/
httpClient?: HttpClient;

/**
* (Advanced) Use your own storage implementation.
* If not provided, the client will use the default local storage implementation, which is AsyncStorage.
*/
storage?: Storage;
}

/**
Expand Down Expand Up @@ -198,4 +205,5 @@ export const Defaults: ExperimentConfig = {
userProvider: null,
exposureTrackingProvider: null,
httpClient: FetchHttpClient,
storage: null,
};
2 changes: 1 addition & 1 deletion src/types/storage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface Storage {
get(key: string): Promise<string>;
get(key: string): Promise<string | null>;
put(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
}