diff --git a/.travis.yml b/.travis.yml index d9e97f4cf..7ed28d782 100644 --- a/.travis.yml +++ b/.travis.yml @@ -82,10 +82,11 @@ jobs: - &packagetest stage: 'Test sub packages' node_js: '12' - before_install: cd packages/datafile-manager + before_install: cd packages/utils - <<: *packagetest before_install: cd packages/event-processor - <<: *packagetest before_install: cd packages/logging - <<: *packagetest - before_install: cd packages/utils + before_script: npm install "@react-native-community/async-storage" + before_install: cd packages/datafile-manager diff --git a/packages/datafile-manager/__mocks__/@react-native-community/async-storage.ts b/packages/datafile-manager/__mocks__/@react-native-community/async-storage.ts new file mode 100644 index 000000000..32f721144 --- /dev/null +++ b/packages/datafile-manager/__mocks__/@react-native-community/async-storage.ts @@ -0,0 +1,37 @@ +/** + * 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. + */ + +export default class AsyncStorage { + static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise { + return new Promise((resolve, reject) => { + switch (key) { + case 'keyThatExists': + resolve('{ "name": "Awesome Object" }') + break + case 'keyThatDoesNotExist': + resolve(null) + break + case 'keyWithInvalidJsonObject': + resolve('bad json }') + break + } + }) + } + + static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise { + return Promise.resolve() + } +} diff --git a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts new file mode 100644 index 000000000..02f4eff0b --- /dev/null +++ b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts @@ -0,0 +1,65 @@ +/** + * 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.json b/packages/datafile-manager/package.json index 5b4ec99cd..43b27f7da 100644 --- a/packages/datafile-manager/package.json +++ b/packages/datafile-manager/package.json @@ -38,6 +38,9 @@ "@optimizely/js-sdk-logging": "^0.1.0", "@optimizely/js-sdk-utils": "^0.1.0" }, + "peerDependencies": { + "@react-native-community/async-storage": "^1.2.0" + }, "scripts": { "test": "jest", "tsc": "rm -rf lib && tsc", diff --git a/packages/datafile-manager/src/persistentKeyValueCache.ts b/packages/datafile-manager/src/persistentKeyValueCache.ts new file mode 100644 index 000000000..92c390cf9 --- /dev/null +++ b/packages/datafile-manager/src/persistentKeyValueCache.ts @@ -0,0 +1,61 @@ +/** + * 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..64c7a70a6 --- /dev/null +++ b/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts @@ -0,0 +1,56 @@ +/** + * 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) + } +}