Skip to content

Commit 3f40af2

Browse files
prepare 3.5.0 release (#55)
1 parent 1c8edd4 commit 3f40af2

17 files changed

+581
-318
lines changed

src/PersistentFlagStore.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as utils from './utils';
2+
3+
export default function PersistentFlagStore(storage, environment, hash, ident) {
4+
const store = {};
5+
6+
function getFlagsKey() {
7+
let key = '';
8+
const user = ident.getUser();
9+
if (user) {
10+
key = hash || utils.btoa(JSON.stringify(user));
11+
}
12+
return 'ld:' + environment + ':' + key;
13+
}
14+
15+
// Returns a Promise which will be resolved with a parsed JSON value if a stored value was available,
16+
// or resolved with null if there was no value or if storage was not available.
17+
store.loadFlags = () =>
18+
storage.get(getFlagsKey()).then(dataStr => {
19+
if (dataStr === null || dataStr === undefined) {
20+
return null;
21+
}
22+
try {
23+
let data = JSON.parse(dataStr);
24+
if (data) {
25+
const schema = data.$schema;
26+
if (schema === undefined || schema < 1) {
27+
data = utils.transformValuesToVersionedValues(data);
28+
} else {
29+
delete data['$schema'];
30+
}
31+
}
32+
return data;
33+
} catch (ex) {
34+
return store.clearFlags().then(() => null);
35+
}
36+
});
37+
38+
// Resolves with true if successful, or false if storage is unavailable. Never rejects.
39+
store.saveFlags = flags => {
40+
const data = utils.extend({}, flags, { $schema: 1 });
41+
return storage.set(getFlagsKey(), JSON.stringify(data));
42+
};
43+
44+
// Resolves with true if successful, or false if storage is unavailable. Never rejects.
45+
store.clearFlags = () => storage.clear(getFlagsKey());
46+
47+
return store;
48+
}

src/PersistentStorage.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as messages from './messages';
2+
3+
// The localStorageProvider is provided by the platform object. It should have the following
4+
// methods, each of which should return a Promise:
5+
// - get(key): Gets the string value, if any, for the given key
6+
// - set(key, value): Stores a string value for the given key
7+
// - remove(key): Removes the given key
8+
//
9+
// Storage is just a light wrapper of the localStorageProvider, adding error handling and
10+
// ensuring that we don't call it if it's unavailable. The get method will simply resolve
11+
// with an undefined value if there is an error or if there is no localStorageProvider.
12+
// None of the promises returned by Storage will ever be rejected.
13+
//
14+
// It is always possible that the underlying platform storage mechanism might fail or be
15+
// disabled. If so, it's likely that it will keep failing, so we will only log one warning
16+
// instead of repetitive warnings.
17+
export default function PersistentStorage(localStorageProvider, logger) {
18+
const storage = {};
19+
let loggedError = false;
20+
21+
const logError = err => {
22+
if (!loggedError) {
23+
loggedError = true;
24+
logger.warn(messages.localStorageUnavailable(err));
25+
}
26+
};
27+
28+
storage.isEnabled = () => !!localStorageProvider;
29+
30+
// Resolves with a value, or undefined if storage is unavailable. Never rejects.
31+
storage.get = key =>
32+
new Promise(resolve => {
33+
if (!localStorageProvider) {
34+
resolve(undefined);
35+
return;
36+
}
37+
localStorageProvider
38+
.get(key)
39+
.then(resolve)
40+
.catch(err => {
41+
logError(err);
42+
resolve(undefined);
43+
});
44+
});
45+
46+
// Resolves with true if successful, or false if storage is unavailable. Never rejects.
47+
storage.set = (key, value) =>
48+
new Promise(resolve => {
49+
if (!localStorageProvider) {
50+
resolve(false);
51+
return;
52+
}
53+
localStorageProvider
54+
.set(key, value)
55+
.then(() => resolve(true))
56+
.catch(err => {
57+
logError(err);
58+
resolve(false);
59+
});
60+
});
61+
62+
// Resolves with true if successful, or false if storage is unavailable. Never rejects.
63+
storage.clear = key =>
64+
new Promise(resolve => {
65+
if (!localStorageProvider) {
66+
resolve(false);
67+
return;
68+
}
69+
localStorageProvider
70+
.clear(key)
71+
.then(() => resolve(true))
72+
.catch(err => {
73+
logError(err);
74+
resolve(false);
75+
});
76+
});
77+
78+
return storage;
79+
}

src/Store.js

Lines changed: 0 additions & 69 deletions
This file was deleted.

src/UserValidator.js

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,13 @@ import * as utils from './utils';
1212

1313
const ldUserIdKey = 'ld:$anonUserId';
1414

15-
export default function UserValidator(localStorageProvider, logger) {
15+
export default function UserValidator(persistentStorage) {
1616
function getCachedUserId() {
17-
if (localStorageProvider) {
18-
return localStorageProvider.get(ldUserIdKey).catch(() => null);
19-
// Not logging errors here, because if local storage fails for the get, it will presumably fail for the set,
20-
// so we will end up logging an error in setCachedUserId anyway.
21-
}
22-
return Promise.resolve(null);
17+
return persistentStorage.get(ldUserIdKey);
2318
}
2419

2520
function setCachedUserId(id) {
26-
if (localStorageProvider) {
27-
return localStorageProvider.set(ldUserIdKey, id).catch(() => {
28-
logger.warn(messages.localStorageUnavailableForUserId());
29-
});
30-
}
31-
return Promise.resolve();
21+
return persistentStorage.set(ldUserIdKey, id);
3222
}
3323

3424
const ret = {};

src/__tests__/ConsoleLogger-test.js

Lines changed: 0 additions & 117 deletions
This file was deleted.

src/__tests__/LDClient-localstorage-test.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,22 @@ describe('LDClient local storage', () => {
8686
});
8787

8888
it('should handle localStorage.get returning an error', async () => {
89-
platform.localStorage.get = () => Promise.reject(new Error());
89+
const myError = new Error('deliberate error');
90+
platform.localStorage.get = () => Promise.reject(myError);
9091
const flags = { 'enable-foo': { value: true } };
9192

9293
await withServer(async server => {
9394
server.byDefault(respondJson(flags));
9495
await withClient(user, { baseUrl: server.url }, async client => {
9596
await client.waitForInitialization();
96-
expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable()]);
97+
expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable(myError)]);
9798
});
9899
});
99100
});
100101

101102
it('should handle localStorage.set returning an error', async () => {
102-
platform.localStorage.set = () => Promise.reject(new Error());
103+
const myError = new Error('deliberate error');
104+
platform.localStorage.set = () => Promise.reject(myError);
103105
const flags = { 'enable-foo': { value: true } };
104106

105107
await withServer(async server => {
@@ -109,7 +111,7 @@ describe('LDClient local storage', () => {
109111

110112
await sleepAsync(0); // allow any pending async tasks to complete
111113

112-
expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable()]);
114+
expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable(myError)]);
113115
});
114116
});
115117
});

0 commit comments

Comments
 (0)