diff --git a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts b/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts index 0b7fcf96c..5044b235f 100644 --- a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts +++ b/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts @@ -18,7 +18,7 @@ import HttpPollingDatafileManager from '../src/httpPollingDatafileManager'; import { Headers, AbortableRequest, Response } from '../src/http'; import { DatafileManagerConfig } from '../src/datafileManager'; import { advanceTimersByTime, getTimerCount } from './testUtils'; -import { PersistentKeyValueCache } from '@optimizely/js-sdk-utils'; +import PersistentKeyValueCache from '../src/persistentKeyValueCache'; jest.mock('../src/backoffController', () => { return jest.fn().mockImplementation(() => { diff --git a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts new file mode 100644 index 000000000..9084434de --- /dev/null +++ b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2020, Optimizely + * + * 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 + * + * http://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. + */ + +import ReactNativeAsyncStorageCache from '../src/reactNativeAsyncStorageCache'; + +describe('reactNativeAsyncStorageCache', () => { + let cacheInstance: ReactNativeAsyncStorageCache; + + beforeEach(() => { + cacheInstance = new ReactNativeAsyncStorageCache(); + }); + + describe('get', function() { + it('should return correct object when item is found in cache', function() { + return cacheInstance.get('keyThatExists').then(v => expect(v).toEqual({ name: 'Awesome Object' })); + }); + + it('should return null if item is not found in cache', function() { + return cacheInstance.get('keyThatDoesNotExist').then(v => expect(v).toBeNull()); + }); + + it('should reject promise error if string has an incorrect JSON format', function() { + return cacheInstance + .get('keyWithInvalidJsonObject') + .catch(() => 'exception caught') + .then(v => { + expect(v).toEqual('exception caught'); + }); + }); + }); + + describe('set', function() { + it('should resolve promise if item was successfully set in the cache', function() { + const testObj = { name: 'Awesome Object' }; + return cacheInstance.set('testKey', testObj); + }); + + it('should reject promise if item was not set in the cache because of json stringifying error', function() { + const testObj: any = { name: 'Awesome Object' }; + testObj.myOwnReference = testObj; + return cacheInstance + .set('testKey', testObj) + .catch(() => 'exception caught') + .then(v => expect(v).toEqual('exception caught')); + }); + }); + + describe('contains', function() { + it('should return true if object with key exists', function() { + return cacheInstance.contains('keyThatExists').then(v => expect(v).toBeTruthy()); + }); + + it('should return false if object with key does not exist', function() { + return cacheInstance.contains('keyThatDoesNotExist').then(v => expect(v).toBeFalsy()); + }); + }); +}); diff --git a/packages/datafile-manager/package-lock.json b/packages/datafile-manager/package-lock.json index 46a1a7bde..87291b020 100644 --- a/packages/datafile-manager/package-lock.json +++ b/packages/datafile-manager/package-lock.json @@ -403,9 +403,9 @@ } }, "@optimizely/js-sdk-utils": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.3.2.tgz", - "integrity": "sha512-CeGzUrpUQkJQM8NMbzr1kK0SKiNOynxEAHKwyLhJrzGOpmQ+qhMW1B8yQlQjHaDmpUsiEZzo4TF8XoqFz9JLXA==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", + "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", "requires": { "uuid": "^3.3.2" } diff --git a/packages/datafile-manager/package.json b/packages/datafile-manager/package.json index 9c7c04e22..8439fa520 100644 --- a/packages/datafile-manager/package.json +++ b/packages/datafile-manager/package.json @@ -13,6 +13,7 @@ }, "main": "lib/index.node.js", "browser": "lib/index.browser.js", + "react-native": "lib/index.react_native.js", "types": "lib/index.d.ts", "directories": { "lib": "lib", @@ -48,7 +49,7 @@ }, "dependencies": { "@optimizely/js-sdk-logging": "^0.1.0", - "@optimizely/js-sdk-utils": "^0.3.2", + "@optimizely/js-sdk-utils": "^0.4.0", "decompress-response": "^4.2.1" }, "peerDependencies": { diff --git a/packages/datafile-manager/src/datafileManager.ts b/packages/datafile-manager/src/datafileManager.ts index 5f8c56951..9e415bfc6 100644 --- a/packages/datafile-manager/src/datafileManager.ts +++ b/packages/datafile-manager/src/datafileManager.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { PersistentKeyValueCache } from '@optimizely/js-sdk-utils'; +import PersistentKeyValueCache from './persistentKeyValueCache'; export interface DatafileUpdate { datafile: object; diff --git a/packages/datafile-manager/src/httpPollingDatafileManager.ts b/packages/datafile-manager/src/httpPollingDatafileManager.ts index 9a4629021..4c41adfc8 100644 --- a/packages/datafile-manager/src/httpPollingDatafileManager.ts +++ b/packages/datafile-manager/src/httpPollingDatafileManager.ts @@ -15,12 +15,13 @@ */ import { getLogger } from '@optimizely/js-sdk-logging'; -import { sprintf, PersistentKeyValueCache } from '@optimizely/js-sdk-utils'; +import { sprintf } from '@optimizely/js-sdk-utils'; import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; import EventEmitter, { Disposer } from './eventEmitter'; import { AbortableRequest, Response, Headers } from './http'; import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } from './config'; import BackoffController from './backoffController'; +import PersistentKeyValueCache from './persistentKeyValueCache'; const logger = getLogger('DatafileManager'); diff --git a/packages/datafile-manager/src/persistentKeyValueCache.ts b/packages/datafile-manager/src/persistentKeyValueCache.ts new file mode 100644 index 000000000..9b3ef9fac --- /dev/null +++ b/packages/datafile-manager/src/persistentKeyValueCache.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2020, Optimizely + * + * 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 + * + * http://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. + */ + +/** + * An Interface to implement a persistent key value cache which supports strings as keys + * and JSON Object as value. + */ +export default interface PersistentKeyValueCache { + /** + * Returns value stored against a key or null if not found. + * @param key + * @returns + * Resolves promise with + * 1. Object if value found was stored as a JSON Object. + * 2. null if the key does not exist in the cache. + * Rejects the promise in case of an error + */ + get(key: string): Promise; + + /** + * Stores Object in the persistent cache against a key + * @param key + * @param val + * @returns + * Resolves promise without a value if successful + * Rejects the promise in case of an error + */ + set(key: string, val: any): Promise; + + /** + * Checks if a key exists in the cache + * @param key + * Resolves promise with + * 1. true if the key exists + * 2. false if the key does not exist + * Rejects the promise in case of an error + */ + contains(key: string): Promise; + + /** + * Removes the key value pair from cache. + * @param key + * Resolves promise without a value if successful + * Rejects the promise in case of an error + */ + remove(key: string): Promise; +} diff --git a/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts b/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts new file mode 100644 index 000000000..73f0bb364 --- /dev/null +++ b/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2020, Optimizely + * + * 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 + * + * http://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. + */ + +import { getLogger } from '@optimizely/js-sdk-logging'; +import AsyncStorage from '@react-native-community/async-storage'; + +import PersistentKeyValueCache from './persistentKeyValueCache'; + +const logger = getLogger('DatafileManager'); + +export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { + get(key: string): Promise { + return AsyncStorage.getItem(key).then((val: string | null) => { + if (!val) { + return null; + } + try { + return JSON.parse(val); + } catch (ex) { + logger.error('Error Parsing Object from cache - %s', ex); + throw ex; + } + }); + } + + set(key: string, val: any): Promise { + try { + return AsyncStorage.setItem(key, JSON.stringify(val)); + } catch (ex) { + logger.error('Error stringifying Object to Json - %s', ex); + return Promise.reject(ex); + } + } + + contains(key: string): Promise { + return AsyncStorage.getItem(key).then((val: string | null) => val !== null); + } + + remove(key: string): Promise { + return AsyncStorage.removeItem(key); + } +} diff --git a/packages/datafile-manager/src/reactNativeDatafileManager.ts b/packages/datafile-manager/src/reactNativeDatafileManager.ts index e12262749..a7a2ac44e 100644 --- a/packages/datafile-manager/src/reactNativeDatafileManager.ts +++ b/packages/datafile-manager/src/reactNativeDatafileManager.ts @@ -18,7 +18,7 @@ import { makeGetRequest } from './browserRequest'; import HttpPollingDatafileManager from './httpPollingDatafileManager'; import { Headers, AbortableRequest } from './http'; import { DatafileManagerConfig } from './datafileManager'; -import { ReactNativeAsyncStorageCache } from '@optimizely/js-sdk-utils'; +import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'; export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest {