Skip to content

Commit 8951a32

Browse files
authored
Merge pull request #252 from Flagsmith/feat/support-multiple-environment-caching
feat: support multiple environment caching
2 parents 3d0e3b2 + f4eaca3 commit 8951a32

File tree

9 files changed

+104
-68
lines changed

9 files changed

+104
-68
lines changed

flagsmith-core.ts

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@ type RequestOptions = {
4141
}
4242

4343
let AsyncStorage: AsyncStorageType = null;
44-
const FLAGSMITH_KEY = "BULLET_TRAIN_DB";
45-
const FLAGSMITH_EVENT = "BULLET_TRAIN_EVENT";
44+
const DEFAULT_FLAGSMITH_KEY = "FLAGSMITH_DB";
45+
const DEFAULT_FLAGSMITH_EVENT = "FLAGSMITH_EVENT";
46+
let FlagsmithEvent = DEFAULT_FLAGSMITH_EVENT;
4647
const defaultAPI = 'https://edge.api.flagsmith.com/api/v1/';
4748
let eventSource: typeof EventSource;
4849
const initError = function(caller: string) {
@@ -80,7 +81,7 @@ const Flagsmith = class {
8081
}
8182

8283
getFlags = () => {
83-
let { api, evaluationContext } = this;
84+
const { api, evaluationContext } = this;
8485
this.log("Get Flags")
8586
this.isLoading = true;
8687

@@ -271,7 +272,7 @@ const Flagsmith = class {
271272
timer: number|null= null
272273
dtrum= null
273274
withTraits?: ITraits|null= null
274-
cacheOptions = {ttl:0, skipAPI: false, loadStale: false}
275+
cacheOptions = {ttl:0, skipAPI: false, loadStale: false, storageKey: undefined as string|undefined}
275276
async init(config: IInitConfig) {
276277
const evaluationContext = toEvaluationContext(config.evaluationContext || {});
277278
try {
@@ -290,7 +291,7 @@ const Flagsmith = class {
290291
enableDynatrace,
291292
enableAnalytics,
292293
realtime,
293-
eventSourceUrl= "https://realtime.flagsmith.com/",
294+
eventSourceUrl= "https://realtime.flagsmith.com/",
294295
AsyncStorage: _AsyncStorage,
295296
identity,
296297
traits,
@@ -331,7 +332,7 @@ const Flagsmith = class {
331332
onError?.(message);
332333
};
333334
this.enableLogs = enableLogs || false;
334-
this.cacheOptions = cacheOptions ? { skipAPI: !!cacheOptions.skipAPI, ttl: cacheOptions.ttl || 0, loadStale: !!cacheOptions.loadStale } : this.cacheOptions;
335+
this.cacheOptions = cacheOptions ? { skipAPI: !!cacheOptions.skipAPI, ttl: cacheOptions.ttl || 0, storageKey:cacheOptions.storageKey, loadStale: !!cacheOptions.loadStale } : this.cacheOptions;
335336
if (!this.cacheOptions.ttl && this.cacheOptions.skipAPI) {
336337
console.warn("Flagsmith: you have set a cache ttl of 0 and are skipping API calls, this means the API will not be hit unless you clear local storage.")
337338
}
@@ -345,6 +346,9 @@ const Flagsmith = class {
345346
this.ticks = 10000;
346347
this.timer = this.enableLogs ? new Date().valueOf() : null;
347348
this.cacheFlags = typeof AsyncStorage !== 'undefined' && !!cacheFlags;
349+
350+
FlagsmithEvent = DEFAULT_FLAGSMITH_EVENT + "_" + evaluationContext.environment.apiKey;
351+
348352
if (_AsyncStorage) {
349353
AsyncStorage = _AsyncStorage;
350354
}
@@ -381,7 +385,7 @@ const Flagsmith = class {
381385
}
382386

383387
if (AsyncStorage && this.canUseStorage) {
384-
AsyncStorage.getItem(FLAGSMITH_EVENT)
388+
AsyncStorage.getItem(FlagsmithEvent)
385389
.then((res)=>{
386390
try {
387391
this.evaluationEvent = JSON.parse(res!) || {}
@@ -398,12 +402,12 @@ const Flagsmith = class {
398402
}
399403

400404
if (AsyncStorage && this.canUseStorage) {
401-
AsyncStorage.getItem(FLAGSMITH_EVENT, (err, res) => {
405+
AsyncStorage.getItem(FlagsmithEvent, (err, res) => {
402406
if (res && this.evaluationContext.environment) {
403407
const json = JSON.parse(res);
404408
if (json[this.evaluationContext.environment.apiKey]) {
405-
const state = this.getState();
406-
this.log("Retrieved events from cache", res);
409+
const state = this.getState();
410+
this.log("Retrieved events from cache", res);
407411
this.setState({
408412
...state,
409413
evaluationEvent: json[this.evaluationContext.environment.apiKey],
@@ -453,7 +457,7 @@ const Flagsmith = class {
453457
...json,
454458
evaluationContext: toEvaluationContext({
455459
...json.evaluationContext,
456-
identity: !!json.evaluationContext?.identity ? {
460+
identity: json.evaluationContext?.identity ? {
457461
...json.evaluationContext?.identity,
458462
traits: {
459463
...json.evaluationContext?.identity?.traits || {},
@@ -510,7 +514,7 @@ const Flagsmith = class {
510514
}
511515
};
512516
try {
513-
const res = AsyncStorage.getItemSync? AsyncStorage.getItemSync(FLAGSMITH_KEY) : await AsyncStorage.getItem(FLAGSMITH_KEY);
517+
const res = AsyncStorage.getItemSync? AsyncStorage.getItemSync(this.getStorageKey()) : await AsyncStorage.getItem(this.getStorageKey());
514518
await onRetrievedStorage(null, res)
515519
} catch (e) {}
516520
}
@@ -538,15 +542,6 @@ const Flagsmith = class {
538542
}
539543
}
540544

541-
private _loadedState(error: any = null, source: FlagSource, isFetching = false) {
542-
return {
543-
error,
544-
isFetching,
545-
isLoading: false,
546-
source
547-
}
548-
}
549-
550545
getAllFlags() {
551546
return this.flags;
552547
}
@@ -660,7 +655,7 @@ const Flagsmith = class {
660655
}
661656

662657
setContext = (clientEvaluationContext: ClientEvaluationContext) => {
663-
let evaluationContext = toEvaluationContext(clientEvaluationContext);
658+
const evaluationContext = toEvaluationContext(clientEvaluationContext);
664659
this.evaluationContext = {
665660
...evaluationContext,
666661
environment: evaluationContext.environment || this.evaluationContext.environment,
@@ -745,6 +740,19 @@ const Flagsmith = class {
745740
return res;
746741
};
747742

743+
private _loadedState(error: any = null, source: FlagSource, isFetching = false) {
744+
return {
745+
error,
746+
isFetching,
747+
isLoading: false,
748+
source
749+
}
750+
}
751+
752+
private getStorageKey = ()=> {
753+
return this.cacheOptions?.storageKey || DEFAULT_FLAGSMITH_KEY + "_" + this.evaluationContext.environment?.apiKey
754+
}
755+
748756
private log(...args: (unknown)[]) {
749757
if (this.enableLogs) {
750758
console.log.apply(this, ['FLAGSMITH:', new Date().valueOf() - (this.timer || 0), 'ms', ...args]);
@@ -756,7 +764,7 @@ const Flagsmith = class {
756764
this.ts = new Date().valueOf();
757765
const state = JSON.stringify(this.getState());
758766
this.log('Setting storage', state);
759-
AsyncStorage!.setItem(FLAGSMITH_KEY, state);
767+
AsyncStorage!.setItem(this.getStorageKey(), state);
760768
}
761769
}
762770

@@ -820,7 +828,7 @@ const Flagsmith = class {
820828
private updateEventStorage() {
821829
if (this.enableAnalytics) {
822830
const events = JSON.stringify(this.getState().evaluationEvent);
823-
AsyncStorage!.setItem(FLAGSMITH_EVENT, events);
831+
AsyncStorage!.setItem(FlagsmithEvent, events);
824832
}
825833
}
826834

lib/flagsmith-es/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "flagsmith-es",
3-
"version": "5.0.0",
3+
"version": "6.0.0",
44
"description": "Feature flagging to support continuous development. This is an esm equivalent of the standard flagsmith npm module.",
55
"main": "./index.js",
66
"type": "module",

lib/flagsmith/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "flagsmith",
3-
"version": "5.0.0",
3+
"version": "6.0.0",
44
"description": "Feature flagging to support continuous development",
55
"main": "./index.js",
66
"repository": {

lib/react-native-flagsmith/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-flagsmith",
3-
"version": "5.0.0",
3+
"version": "6.0.0",
44
"description": "Feature flagging to support continuous development",
55
"main": "./index.js",
66
"repository": {

test/cache.test.ts

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Sample test
21
import {
32
defaultState,
43
defaultStateAlt,
4+
FLAGSMITH_KEY,
55
getFlagsmith,
66
getStateToCheck,
77
identityState,
@@ -33,7 +33,21 @@ describe('Cache', () => {
3333
onChange,
3434
});
3535
await flagsmith.init(initConfig);
36-
const cache = await AsyncStorage.getItem('BULLET_TRAIN_DB');
36+
const cache = await AsyncStorage.getItem(FLAGSMITH_KEY);
37+
expect(getStateToCheck(JSON.parse(`${cache}`))).toEqual(defaultState);
38+
});
39+
test('should set cache after init with custom key', async () => {
40+
const onChange = jest.fn();
41+
const customKey = 'custom_key';
42+
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
43+
cacheFlags: true,
44+
cacheOptions: {
45+
storageKey: customKey,
46+
},
47+
onChange,
48+
});
49+
await flagsmith.init(initConfig);
50+
const cache = await AsyncStorage.getItem(customKey);
3751
expect(getStateToCheck(JSON.parse(`${cache}`))).toEqual(defaultState);
3852
});
3953
test('should call onChange with cache then eventually with an API response', async () => {
@@ -53,7 +67,7 @@ describe('Cache', () => {
5367
cacheFlags: true,
5468
onChange,
5569
});
56-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify(defaultStateAlt));
70+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify(defaultStateAlt));
5771
await flagsmith.init(initConfig);
5872

5973
// Flags retrieved from cache
@@ -86,7 +100,7 @@ describe('Cache', () => {
86100
identity: testIdentity,
87101
onChange,
88102
});
89-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
103+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
90104
...defaultStateAlt,
91105
identity: 'bad_identity',
92106
}));
@@ -102,7 +116,7 @@ describe('Cache', () => {
102116
onChange,
103117
cacheOptions: { ttl: 1 },
104118
});
105-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
119+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
106120
...defaultStateAlt,
107121
ts: new Date().valueOf() - 100,
108122
}));
@@ -120,7 +134,7 @@ describe('Cache', () => {
120134
onChange,
121135
cacheOptions: { ttl: 1, loadStale: true },
122136
});
123-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
137+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
124138
...defaultStateAlt,
125139
ts: new Date().valueOf() - 100,
126140
}));
@@ -138,7 +152,7 @@ describe('Cache', () => {
138152
onChange,
139153
cacheOptions: { ttl: 1000 },
140154
});
141-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
155+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
142156
...defaultStateAlt,
143157
ts: new Date().valueOf(),
144158
}));
@@ -155,7 +169,7 @@ describe('Cache', () => {
155169
cacheFlags: false,
156170
onChange,
157171
});
158-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
172+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
159173
...defaultStateAlt,
160174
ts: new Date().valueOf(),
161175
}));
@@ -173,25 +187,7 @@ describe('Cache', () => {
173187
onChange,
174188
cacheOptions: { ttl: 1000, skipAPI: true },
175189
});
176-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
177-
...defaultStateAlt,
178-
ts: new Date().valueOf(),
179-
}));
180-
await flagsmith.init(initConfig);
181-
expect(onChange).toHaveBeenCalledTimes(1);
182-
expect(mockFetch).toHaveBeenCalledTimes(0);
183-
expect(getStateToCheck(flagsmith.getState())).toEqual({
184-
...defaultStateAlt,
185-
});
186-
});
187-
test('should not get flags from API when skipAPI is set', async () => {
188-
const onChange = jest.fn();
189-
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
190-
cacheFlags: true,
191-
onChange,
192-
cacheOptions: { ttl: 1000, skipAPI: true },
193-
});
194-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
190+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
195191
...defaultStateAlt,
196192
ts: new Date().valueOf(),
197193
}));
@@ -209,7 +205,7 @@ describe('Cache', () => {
209205
onChange,
210206
cacheOptions: { ttl: 1, skipAPI: true, loadStale: true },
211207
});
212-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
208+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
213209
...defaultStateAlt,
214210
ts: new Date().valueOf() - 100,
215211
}));
@@ -220,14 +216,15 @@ describe('Cache', () => {
220216
...defaultStateAlt,
221217
});
222218
});
219+
223220
test('should validate flags are unchanged when fetched', async () => {
224221
const onChange = jest.fn();
225222
const { flagsmith, initConfig, AsyncStorage, mockFetch } = getFlagsmith({
226223
onChange,
227224
cacheFlags: true,
228225
preventFetch: true,
229226
});
230-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
227+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
231228
...defaultState,
232229
}));
233230
await flagsmith.init(initConfig);
@@ -273,7 +270,7 @@ describe('Cache', () => {
273270
preventFetch: true,
274271
defaultFlags: defaultState.flags,
275272
});
276-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
273+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
277274
...defaultState,
278275
}));
279276
await flagsmith.init(initConfig);
@@ -319,7 +316,7 @@ describe('Cache', () => {
319316
preventFetch: true,
320317
});
321318
const storage = new SyncStorageMock();
322-
await storage.setItem('BULLET_TRAIN_DB', JSON.stringify({
319+
await storage.setItem(FLAGSMITH_KEY, JSON.stringify({
323320
...defaultState,
324321
}));
325322
flagsmith.init({
@@ -345,7 +342,7 @@ describe('Cache', () => {
345342
preventFetch: true,
346343
});
347344
const storage = new SyncStorageMock();
348-
await storage.setItem('BULLET_TRAIN_DB', JSON.stringify({
345+
await storage.setItem(FLAGSMITH_KEY, JSON.stringify({
349346
...identityState,
350347
}));
351348
const ts = Date.now();
@@ -356,8 +353,8 @@ describe('Cache', () => {
356353
});
357354
expect(flagsmith.getAllTraits()).toEqual({
358355
...identityState.traits,
359-
ts
360-
})
356+
ts,
357+
});
361358
});
362359
test('should cache transient traits correctly', async () => {
363360
const onChange = jest.fn();
@@ -395,4 +392,4 @@ describe('Cache', () => {
395392
},
396393
})
397394
});
398-
});
395+
});

test/default-flags.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Sample test
2-
import { defaultState, defaultStateAlt, getFlagsmith, getStateToCheck } from './test-constants';
2+
import { defaultState, defaultStateAlt, FLAGSMITH_KEY, getFlagsmith, getStateToCheck } from './test-constants';
33
import { IFlags } from '../types';
44

55
describe('Default Flags', () => {
@@ -51,7 +51,7 @@ describe('Default Flags', () => {
5151
cacheFlags: true,
5252
defaultFlags: {...defaultFlags, ...itemsToRemove},
5353
});
54-
await AsyncStorage.setItem('BULLET_TRAIN_DB', JSON.stringify({
54+
await AsyncStorage.setItem(FLAGSMITH_KEY, JSON.stringify({
5555
...defaultState,
5656
flags: {
5757
...defaultFlags,

0 commit comments

Comments
 (0)