diff --git a/src/__tests__/LDClient-localstorage-test.js b/src/__tests__/LDClient-localstorage-test.js index 4b0da64..0df1ca9 100644 --- a/src/__tests__/LDClient-localstorage-test.js +++ b/src/__tests__/LDClient-localstorage-test.js @@ -137,4 +137,125 @@ describe('LDClient local storage', () => { }); }); }); + + describe('bootstrapping from session storage', () => { + it('does not try to use session storage if the platform says it is unavailable', async () => { + const platform = stubPlatform.defaults(); + platform.localStorage = null; + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + await client.waitForInitialization(); + + // should see a flag request to the server right away, as if bootstrap was not specified + expect(server.requests.length).toEqual(1); + + expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable()]); + }); + + it('uses cached flags if available and requests flags from server after ready', async () => { + const platform = stubPlatform.defaults(); + const json = '{"flag-key": 1}'; + platform.testing.setLocalStorageImmediately(lsKey, json); + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + await client.waitForInitialization(); + + expect(client.variation('flag-key')).toEqual(1); + expect(server.requests.length).toEqual(1); + }); + + it('starts with empty flags and requests them from server if there are no cached flags', async () => { + const platform = stubPlatform.defaults(); + server.respondWith(jsonResponse({ 'flag-key': { value: 1 } })); + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + + // don't wait for ready event - verifying that variation() doesn't throw an error if called before ready + expect(client.variation('flag-key', 0)).toEqual(0); + + // verify that the flags get requested from LD + await client.waitForInitialization(); + expect(client.variation('flag-key')).toEqual(1); + }); + + it('should handle sessionStorage.get returning an error', async () => { + const platform = stubPlatform.defaults(); + platform.localStorage.get = () => Promise.reject(new Error()); + server.respondWith(jsonResponse({ 'enable-foo': { value: true } })); + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + await client.waitForInitialization(); + + expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable()]); + }); + + it('should handle sessionStorage.set returning an error', async () => { + const platform = stubPlatform.defaults(); + platform.localStorage.set = () => Promise.reject(new Error()); + server.respondWith(jsonResponse({ 'enable-foo': { value: true } })); + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + await client.waitForInitialization(); + + await asyncSleep(0); // allow any pending async tasks to complete + + expect(platform.testing.logger.output.warn).toEqual([messages.localStorageUnavailable()]); + }); + + it('should not update cached settings if there was an error fetching flags', async () => { + const platform = stubPlatform.defaults(); + const json = '{"enable-foo": true}'; + server.respondWith(errorResponse(503)); + platform.testing.setLocalStorageImmediately(lsKey, json); + + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + await client.waitForInitialization(); + + await asyncSleep(0); // allow any pending async tasks to complete + + const value = platform.testing.getLocalStorageImmediately(lsKey); + expect(value).toEqual(json); + }); + + it('should use hash as sessionStorage key when secure mode is enabled', async () => { + const platform = stubPlatform.defaults(); + server.respondWith(jsonResponse({ 'enable-foo': { value: true } })); + const lsKeyHash = 'ld:UNKNOWN_ENVIRONMENT_ID:totallyLegitHash'; + const client = platform.testing.makeClient(envName, user, { + bootstrap: 'sessionStorage', + hash: 'totallyLegitHash', + }); + + await client.waitForInitialization(); + const value = platform.testing.getLocalStorageImmediately(lsKeyHash); + expect(JSON.parse(value)).toEqual({ + $schema: 1, + 'enable-foo': { value: true }, + }); + }); + + it('should clear sessionStorage when user context is changed', async () => { + const platform = stubPlatform.defaults(); + const lsKey2 = 'ld:UNKNOWN_ENVIRONMENT_ID:' + utils.btoa('{"key":"user2"}'); + + const user2 = { key: 'user2' }; + const client = platform.testing.makeClient(envName, user, { bootstrap: 'sessionStorage' }); + + server.respondWith(jsonResponse({ 'enable-foo': { value: true } })); + + await client.waitForInitialization(); + + await asyncSleep(0); // allow any pending async tasks to complete + + await client.identify(user2); + + const value1 = platform.testing.getLocalStorageImmediately(lsKey); + expect(value1).not.toEqual(expect.anything()); + const value2 = platform.testing.getLocalStorageImmediately(lsKey2); + expect(JSON.parse(value2)).toEqual({ + $schema: 1, + 'enable-foo': { value: true }, + }); + }); + }); }); diff --git a/src/index.js b/src/index.js index 9cbbad6..dacff07 100644 --- a/src/index.js +++ b/src/index.js @@ -507,7 +507,10 @@ export function initialize(env, user, specifiedOptions, platform, extraDefaults) }); }); - if (typeof options.bootstrap === 'string' && options.bootstrap.toUpperCase() === 'LOCALSTORAGE') { + if (typeof options.bootstrap === 'string' && + // Support local or session storage names being passed as a bootstrap option. + ['SESSIONSTORAGE', 'LOCALSTORAGE'].includes(options.bootstrap.toUpperCase()) + ) { if (store) { useLocalStorage = true; } else {