diff --git a/src/LeanplumInbox.ts b/src/LeanplumInbox.ts index 62118ce2..8713795f 100644 --- a/src/LeanplumInbox.ts +++ b/src/LeanplumInbox.ts @@ -1,5 +1,6 @@ import ArgsBuilder from './ArgsBuilder' import Constants from './Constants' +import StorageManager from './StorageManager' import { CreateRequestFunction, MessageObject } from './types/internal' import { Action, Inbox, InboxMessage } from './types/public' @@ -85,14 +86,18 @@ export default class LeanplumInbox implements Inbox { } private save(): void { - sessionStorage.setItem( + StorageManager.save( Constants.DEFAULT_KEYS.INBOX_MESSAGES, - JSON.stringify(this.messageMap) + JSON.stringify(this.messageMap), + 'session' ) } private load(): void { - const state = sessionStorage.getItem(Constants.DEFAULT_KEYS.INBOX_MESSAGES) + const state = StorageManager.get( + Constants.DEFAULT_KEYS.INBOX_MESSAGES, + 'session' + ) this.messageMap = JSON.parse(state) || {} } diff --git a/src/LeanplumInternal.ts b/src/LeanplumInternal.ts index 3b81a2eb..d99e923e 100644 --- a/src/LeanplumInternal.ts +++ b/src/LeanplumInternal.ts @@ -21,7 +21,7 @@ import InternalState from './InternalState' import LeanplumInbox from './LeanplumInbox' import LeanplumRequest from './LeanplumRequest' import LeanplumSocket from './LeanplumSocket' -import LocalStorageManager from './LocalStorageManager' +import StorageManager from './StorageManager' import PushManager from './PushManager' import Messages from './Messages' import EventEmitter from './EventEmitter' @@ -406,7 +406,7 @@ Use "npm update leanplum-sdk" or go to https://docs.leanplum.com/reference#javas sendNow: true, queued: true, response: () => { - LocalStorageManager.removeFromLocalStorage(SESSION_KEY) + StorageManager.remove(SESSION_KEY) }, }) } @@ -473,7 +473,7 @@ Use "npm update leanplum-sdk" or go to https://docs.leanplum.com/reference#javas if (userId) { this._lpRequest.userId = userId - LocalStorageManager.saveToLocalStorage(Constants.DEFAULT_KEYS.USER_ID, this._lpRequest.userId) + StorageManager.save(Constants.DEFAULT_KEYS.USER_ID, this._lpRequest.userId) } } @@ -635,7 +635,7 @@ Use "npm update leanplum-sdk" or go to https://docs.leanplum.com/reference#javas } const currentTime = Date.now() - const lastActive = parseInt(LocalStorageManager.getFromLocalStorage(SESSION_KEY)) + const lastActive = parseInt(StorageManager.get(SESSION_KEY)) if (isNaN(lastActive)) { return false @@ -649,6 +649,6 @@ Use "npm update leanplum-sdk" or go to https://docs.leanplum.com/reference#javas } private updateSession(): void { - LocalStorageManager.saveToLocalStorage(SESSION_KEY, String(Date.now())) + StorageManager.save(SESSION_KEY, String(Date.now())) } } diff --git a/src/LeanplumRequest.ts b/src/LeanplumRequest.ts index 2bf0ec36..4333fe8e 100644 --- a/src/LeanplumRequest.ts +++ b/src/LeanplumRequest.ts @@ -17,7 +17,7 @@ import ArgsBuilder from './ArgsBuilder' import Constants from './Constants' -import LocalStorageManager from './LocalStorageManager' +import StorageManager from './StorageManager' import Network from './Network' export default class LeanplumRequest { @@ -214,15 +214,15 @@ export default class LeanplumRequest { } private loadLocal(key: string): T { - return LocalStorageManager.getFromLocalStorage(key) + return StorageManager.get(key) } private saveLocal(key: string, value: T): void { - LocalStorageManager.saveToLocalStorage(key, value) + StorageManager.save(key, value) } private removeLocal(key: string): void { - LocalStorageManager.removeFromLocalStorage(key) + StorageManager.remove(key) } } diff --git a/src/LocalStorageManager.ts b/src/LocalStorageManager.ts deleted file mode 100644 index ddf1ecaa..00000000 --- a/src/LocalStorageManager.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * - * Copyright 2020 Leanplum Inc. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - * - */ - -let localStorageEnabled -const alternateLocalStorage = {} - -export default class LocalStorageManager { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - static getFromLocalStorage(key): any { - if (localStorageEnabled === false) { - return alternateLocalStorage[key] - } - - return localStorage[key] - } - - static saveToLocalStorage(key, value): void { - if (localStorageEnabled === false) { - alternateLocalStorage[key] = value - return - } - - try { - localStorage[key] = value - } catch (e) { - localStorageEnabled = false - alternateLocalStorage[key] = value - } - } - - static removeFromLocalStorage(key): void { - if (localStorageEnabled === false) { - delete alternateLocalStorage[key] - return - } - - try { - localStorage.removeItem(key) - } catch (e) { - localStorageEnabled = false - delete alternateLocalStorage[key] - } - } -} diff --git a/src/Messages.ts b/src/Messages.ts index 595c7ed2..55d07053 100644 --- a/src/Messages.ts +++ b/src/Messages.ts @@ -5,7 +5,7 @@ import { CreateRequestFunction, Message, MessageVariables } from './types/intern import EventEmitter from './EventEmitter' import Network from './Network' import isEqual from 'lodash.isequal' -import LocalStorageManager from './LocalStorageManager' +import StorageManager from './StorageManager' import ValueTransforms from './ValueTransforms' /* eslint-disable @typescript-eslint/ban-types */ @@ -76,7 +76,7 @@ class OccurrenceTracker { } load(): void { - const cache = LocalStorageManager.getFromLocalStorage(Constants.DEFAULT_KEYS.MESSAGE_OCCURRENCES) + const cache = StorageManager.get(Constants.DEFAULT_KEYS.MESSAGE_OCCURRENCES) if (cache) { const json = JSON.parse(cache) this.session = json.session @@ -87,7 +87,7 @@ class OccurrenceTracker { save(): void { const key = Constants.DEFAULT_KEYS.MESSAGE_OCCURRENCES - LocalStorageManager.saveToLocalStorage(key, JSON.stringify({ + StorageManager.save(key, JSON.stringify({ session: this.session, triggers: this.triggers, occurrences: this.occurrences, @@ -131,7 +131,7 @@ export default class Messages { }) events.on('resume', () => { const key = Constants.DEFAULT_KEYS.MESSAGE_CACHE - const cache = LocalStorageManager.getFromLocalStorage(key) + const cache = StorageManager.get(key) this._messageCache = cache ? JSON.parse(cache) : this._messageCache this.occurrenceTracker.load() @@ -201,7 +201,7 @@ export default class Messages { onMessagesReceived(receivedMessages): void { const messages = receivedMessages || {} this._messageCache = messages - LocalStorageManager.saveToLocalStorage(Constants.DEFAULT_KEYS.MESSAGE_CACHE, JSON.stringify(messages)) + StorageManager.save(Constants.DEFAULT_KEYS.MESSAGE_CACHE, JSON.stringify(messages)) } shouldShowMessage(id: string, message, context: TriggerContext): boolean { diff --git a/src/PushManager.ts b/src/PushManager.ts index 341062e4..70d241eb 100644 --- a/src/PushManager.ts +++ b/src/PushManager.ts @@ -17,7 +17,7 @@ import ArgsBuilder from './ArgsBuilder' import Constants from './Constants' -import LocalStorageManager from './LocalStorageManager' +import StorageManager from './StorageManager' import { CreateRequestFunction } from './types/internal' const APPLICATION_SERVER_PUBLIC_KEY = @@ -193,12 +193,12 @@ export default class PushManager { if (subscription) { const preparedSubscription = this.prepareSubscription(subscription) const preparedSubscriptionString = JSON.stringify(preparedSubscription) - const existingSubscriptionString = LocalStorageManager.getFromLocalStorage( + const existingSubscriptionString = StorageManager.get( Constants.DEFAULT_KEYS.PUSH_SUBSCRIPTION ) as string if (existingSubscriptionString !== preparedSubscriptionString) { - LocalStorageManager.saveToLocalStorage( + StorageManager.save( Constants.DEFAULT_KEYS.PUSH_SUBSCRIPTION, preparedSubscriptionString ) diff --git a/src/StorageManager.ts b/src/StorageManager.ts new file mode 100644 index 00000000..4bce869f --- /dev/null +++ b/src/StorageManager.ts @@ -0,0 +1,80 @@ +/* + * + * Copyright 2020 Leanplum Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License + * + */ + +const storageEnabled = { + local: true, + session: true, +} +const alternateStorage = { + local: {}, + session: {}, +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Value = any +type StorageType = 'local' | 'session' + +export default class StorageManager { + static get(key: string, type: StorageType = 'local'): Value { + if (!storageEnabled[type]) { + return alternateStorage[type][key] + } + + if (type === 'local') { + return localStorage[key] + } else { + return sessionStorage.getItem(key) + } + } + + static save(key: string, value: Value, type: StorageType = 'local'): void { + if (!storageEnabled[type]) { + alternateStorage[type][key] = value + return + } + + try { + if (type === 'local') { + localStorage[key] = value + } else { + sessionStorage.setItem(key, value) + } + } catch (e) { + storageEnabled[type] = false + alternateStorage[type][key] = value + } + } + + static remove(key: string, type: StorageType = 'local'): void { + if (!storageEnabled[type]) { + delete alternateStorage[type][key] + return + } + + try { + if (type === 'local') { + localStorage.removeItem(key) + } else { + sessionStorage.removeItem(key) + } + } catch (e) { + storageEnabled[type] = false + delete alternateStorage[type][key] + } + } +} diff --git a/src/VarCache.ts b/src/VarCache.ts index a8e44473..3cae1d24 100644 --- a/src/VarCache.ts +++ b/src/VarCache.ts @@ -16,7 +16,7 @@ import ArgsBuilder from './ArgsBuilder' import Constants from './Constants' -import LocalStorageManager from './LocalStorageManager' +import StorageManager from './StorageManager' import { CreateRequestFunction } from './types/internal' import { ActionParameter, MessageTemplateOptions } from './types/public' import ValueTransforms from './ValueTransforms' @@ -188,11 +188,11 @@ export default class VarCache { } private loadLocal(key: string): T { - return LocalStorageManager.getFromLocalStorage(key) + return StorageManager.get(key) } private saveLocal(key: string, value: T): void { - LocalStorageManager.saveToLocalStorage(key, value) + StorageManager.save(key, value) } } diff --git a/test/specs/LeanplumInbox.test.ts b/test/specs/LeanplumInbox.test.ts index d6123b2b..0d6c3997 100644 --- a/test/specs/LeanplumInbox.test.ts +++ b/test/specs/LeanplumInbox.test.ts @@ -41,7 +41,7 @@ describe(LeanplumInbox, () => { inbox.onChanged(handler) createRequestSpy.mockImplementationOnce( - (method, args, options) => { + (_, __, options) => { options.response({ response: [{ success:true, @@ -64,7 +64,7 @@ describe(LeanplumInbox, () => { inbox.onChanged(handler) createRequestSpy.mockImplementationOnce( - (method, args, options) => { + (_, __, options) => { options.response(null) } ) @@ -441,7 +441,7 @@ describe(LeanplumInbox, () => { function mockMessages(newsfeedMessages: any): void { createRequestSpy.mockImplementationOnce( - (method, args, options) => { + (_, __, options) => { options.response({ response: [ { newsfeedMessages } ], }) diff --git a/test/specs/LeanplumIntegration.test.ts b/test/specs/LeanplumIntegration.test.ts index f2bb43ee..b4d01ba4 100644 --- a/test/specs/LeanplumIntegration.test.ts +++ b/test/specs/LeanplumIntegration.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import Constants from '../../src/Constants' import LeanplumInternal from '../../src/LeanplumInternal' import { startResponse } from '../data/responses' import { windowMock } from '../mocks/external' diff --git a/test/specs/LeanplumRequest.test.ts b/test/specs/LeanplumRequest.test.ts index 2114ec8b..baa15b45 100644 --- a/test/specs/LeanplumRequest.test.ts +++ b/test/specs/LeanplumRequest.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import LeanplumRequest from '../../src/LeanplumRequest' -import LocalStorageManager from '../../src/LocalStorageManager' +import StorageManager from '../../src/StorageManager' import Constants from '../../src/Constants' import Network from '../../src/Network' import ArgsBuilder from '../../src/ArgsBuilder' @@ -32,7 +32,7 @@ describe(LeanplumRequest, () => { describe('userId resolution', () => { beforeEach(() => { - lsGetSpy = jest.spyOn(LocalStorageManager, 'getFromLocalStorage').mockReturnValue(undefined) + lsGetSpy = jest.spyOn(StorageManager, 'get').mockReturnValue(undefined) }) afterEach(() => { diff --git a/test/specs/Messages.test.ts b/test/specs/Messages.test.ts index 3bfcca26..5d8ebe46 100644 --- a/test/specs/Messages.test.ts +++ b/test/specs/Messages.test.ts @@ -1,6 +1,6 @@ import EventEmitter from '../../src/EventEmitter' import Messages from '../../src/Messages' -import LocalStorageManager from '../../src/LocalStorageManager' +import StorageManager from '../../src/StorageManager' import Network from '../../src/Network' describe(Messages, () => { @@ -15,7 +15,7 @@ describe(Messages, () => { localStorage.clear() Network.prototype.ajax = jest.fn() events = new EventEmitter() - createRequest = jest.fn().mockImplementation((m, e, options) => options?.response()) + createRequest = jest.fn().mockImplementation((_, __, options) => options?.response()) getFileUrl = jest.fn() messages = new Messages(events, createRequest, getFileUrl) @@ -872,9 +872,7 @@ describe(Messages, () => { describe('persistence', () => { it('persists message cache', () => { - const now = Date.now() - - jest.spyOn(LocalStorageManager, 'saveToLocalStorage').mockImplementation(() => {}) + jest.spyOn(StorageManager, 'save').mockImplementation(() => {}) const message = { ...MESSAGE_WITH_EVENT_TRIGGER, whenLimits: { @@ -884,12 +882,12 @@ describe(Messages, () => { } events.emit('messagesReceived', { "123": message }) - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalled() - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalledWith('__leanplum_message_cache', JSON.stringify({ '123': message })) + expect(StorageManager.save).toHaveBeenCalled() + expect(StorageManager.save).toHaveBeenCalledWith('__leanplum_message_cache', JSON.stringify({ '123': message })) }) it('loads messages from localStorage', () => { - jest.spyOn(LocalStorageManager, 'getFromLocalStorage').mockImplementation( + jest.spyOn(StorageManager, 'get').mockImplementation( (key) => { if (key === '__leanplum_message_cache') { return JSON.stringify({ @@ -915,7 +913,7 @@ describe(Messages, () => { it('persists message occurrences', () => { const now = Date.now() - jest.spyOn(LocalStorageManager, 'saveToLocalStorage').mockImplementation(() => {}) + jest.spyOn(StorageManager, 'save').mockImplementation(() => {}) events.emit('messagesReceived', { "123": { ...MESSAGE_WITH_EVENT_TRIGGER, whenLimits: { @@ -927,8 +925,8 @@ describe(Messages, () => { events.emit('track', { eventName: 'Add to cart' }) showMessage.mock.calls[0][0].context.track() - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalled() - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalledWith('__leanplum_message_occurrences', JSON.stringify({ + expect(StorageManager.save).toHaveBeenCalled() + expect(StorageManager.save).toHaveBeenCalledWith('__leanplum_message_occurrences', JSON.stringify({ session: { '123': 1 }, triggers: { '123': [ now ] }, occurrences: { '123': [ now ] }, @@ -938,7 +936,7 @@ describe(Messages, () => { it('persists trigger occurrences, even if message limits are enforced', () => { const now = Date.now() - jest.spyOn(LocalStorageManager, 'saveToLocalStorage').mockImplementation(() => {}) + jest.spyOn(StorageManager, 'save').mockImplementation(() => {}) events.emit('messagesReceived', { "123": { ...MESSAGE_WITH_EVENT_TRIGGER, whenLimits: { @@ -949,8 +947,8 @@ describe(Messages, () => { events.emit('track', { eventName: 'Add to cart' }) - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalled() - expect(LocalStorageManager.saveToLocalStorage).toHaveBeenCalledWith('__leanplum_message_occurrences', JSON.stringify({ + expect(StorageManager.save).toHaveBeenCalled() + expect(StorageManager.save).toHaveBeenCalledWith('__leanplum_message_occurrences', JSON.stringify({ session: {}, triggers: { '123': [ now ] }, occurrences: {}, @@ -960,7 +958,7 @@ describe(Messages, () => { it('loads message occurrences from localStorage', () => { const now = Date.now() - jest.spyOn(LocalStorageManager, 'getFromLocalStorage').mockImplementation( + jest.spyOn(StorageManager, 'get').mockImplementation( (key) => { if (key === '__leanplum_message_cache') { return JSON.stringify({ @@ -1000,8 +998,8 @@ describe(Messages, () => { }, } }) - jest.spyOn(LocalStorageManager, 'getFromLocalStorage').mockImplementation( - (key) => { + jest.spyOn(StorageManager, 'get').mockImplementation( + () => { return JSON.stringify({ session: { '123': 1 }, triggers: { '123': [ now ] }, @@ -1169,7 +1167,7 @@ describe(Messages, () => { Document.prototype.createElement = jest.fn(() => iframe) jest.spyOn(Network.prototype, 'ajax').mockImplementationOnce( - (method, type, data, success) => success('##Vars##') + (_, __, ___, success) => success('##Vars##') ) const vars = { __name__: "HTML", Template: TEMPLATE_FILENAME } events.emit('messagesReceived', { "12345": { diff --git a/test/specs/VarCache.test.ts b/test/specs/VarCache.test.ts index a3d6fd5a..78231e60 100644 --- a/test/specs/VarCache.test.ts +++ b/test/specs/VarCache.test.ts @@ -1,5 +1,5 @@ import Constants from '../../src/Constants' -import LocalStorageManager from '../../src/LocalStorageManager' +import StorageManager from '../../src/StorageManager' import VarCache from '../../src/VarCache' import { ActionParameterType, MessageKind } from '../../src/types/public' @@ -81,10 +81,10 @@ describe(VarCache, () => { }) describe('loadDiffs', () => { - let spy: jest.Mocked + let spy: jest.Mocked beforeEach(() => { - spy = jest.spyOn(LocalStorageManager, 'getFromLocalStorage') + spy = jest.spyOn(StorageManager, 'get') }) afterEach(() => { @@ -104,10 +104,10 @@ describe(VarCache, () => { }) describe('saveDiffs', () => { - let spy: jest.Mocked + let spy: jest.Mocked beforeEach(() => { - spy = jest.spyOn(LocalStorageManager, 'saveToLocalStorage') + spy = jest.spyOn(StorageManager, 'save') }) afterEach(() => {