UNPKG

@applicaster/zapp-react-dom-app

Version:

Zapp App Component for Applicaster's Quick Brick React Native App

216 lines (188 loc) • 5.89 kB
// @flow // @ts-nocheck // @ts-ignore import * as R from "ramda"; import { noop, parseJsonIfNeeded, } from "@applicaster/zapp-react-native-utils/functionUtils"; import { mapKeys } from "@applicaster/zapp-react-native-utils/objectUtils"; export type StorageI = { setItem: (string, string, string) => Promise<any>; getItem: (string, string) => Promise<any>; getAllItems: () => Promise<any>; }; export const STORAGE_TYPES = ["localStorage", "sessionStorage"]; export const DEFAULT_NAMESPACE = "applicaster.v2"; const NAMESPACE_SEPARATOR = "_::_"; /** * returns a key with the namespace, if provided * @param {String} key * @param {?String} namespace * @returns {String} */ export function applyNamespaceToKeyName( key: string, namespace: ?string ): string { return namespace ? `${namespace}${NAMESPACE_SEPARATOR}${key}` : key; } /** * this function will try to invoke a function, * run JSON.parse on its result, then resolve. If any error is caught * it will reject with the error. JSON parsing won't yield an error since we * use a method which will return its input if the parsing fails * @param {Function} fn function to run * @returns {Promise} */ function tryAndResolve(fn: (any) => any): Promise<any> { try { return R.compose( (value) => Promise.resolve(value), parseJsonIfNeeded, fn )(); } catch (error) { return Promise.reject(error); } } /** * Gets a storage object from the window global object for web environment * if the storage type required is not implemented for some reason, a warning will * be sent, and a noop function will be returned. Will otherwise return an object with * methods to interact with the storage * @param {string} type of storage (localStorage or sessionStorage) * @returns {Object} storageObject * @returns {Function} storageObject.callMethod: invokes methods on the storage object * @returns {Function} storageObject.getKeys: returns all existing keys in the storage object */ function getStorageObject(type: string): (any) => void | Promise<any> { const storage = R.prop(type, window); if (!R.includes(type, STORAGE_TYPES) || !storage) { // eslint-disable-next-line no-console console.warn(`Storage type ${type} does not exist in this environment`); return { callMethod: noop, }; } /** * calls a method on the storage object * @param {String} method to invoke * @param {Array} args array of arguments to use when invoking the method * @returns response of the method call */ function callMethod(method: string, ...args: [any]): Promise<any> { return storage[method](...args); } /** * returns the list of keys in the storage object * @returns {Array<String>} */ function getKeys() { // the test mock doesn't have exactly the same implementation for returning // allkeys, hence this check if (process.env.NODE_ENV === "test") { return storage.keys; } return R.keys(storage); } return { callMethod, getKeys, }; } /** * returns an object to interact with a storage module (localStorage or sessionStorage) * @param {String} type of storage * @returns {Object} */ export function getStorageModule(type: string): StorageI { const Storage = getStorageObject(type); /** * sets an item in storage. Returns a promise which will * resolve to true if the value was set, or reject with an Error if not * @param {String} key * @param {String} value (already stringified) * @param {?String} namespace * @returns {Promise<Boolean|Error>} */ function setItem( key: string, value: string, namespace: ?string = DEFAULT_NAMESPACE ): Promise<boolean | Error> { const keyName = applyNamespaceToKeyName( key, namespace || DEFAULT_NAMESPACE ); return tryAndResolve(() => { Storage.callMethod("setItem", keyName, value); return true; }); } /** * Retrieves an item from storage. Returns a promise which will resolve * with the value if it exists, or reject with an Error if not. * @param {String} key * @param {?String} namespace * @returns {Promise<Any|Error>} */ function getItem( key: string, namespace: ?string = DEFAULT_NAMESPACE ): Promise<any> { const keyName = applyNamespaceToKeyName( key, namespace || DEFAULT_NAMESPACE ); return tryAndResolve(() => Storage.callMethod("getItem", keyName)); } /** * Removes an item from storage. Returns a promise which will resolve * with a true value if it succeeds, or reject with an Error if not. * @param {String} key * @param {?String} namespace * @returns {Promise<Boolean|Error>} */ function removeItem(key: string, namespace = DEFAULT_NAMESPACE) { const keyName = applyNamespaceToKeyName(key, namespace); return tryAndResolve(() => Storage.callMethod("removeItem", keyName)); } /** * retrieves all items in storage, belonging to the namespace if provided * @param {?String} namespace * @returns {Promise<Object|Error>} */ function getAllItems(namespace: ?string = null): Promise<string> { return tryAndResolve(() => { const keys = Storage.getKeys(); const filteredKeys = R.filter( R.ifElse( () => namespace, (R.contains || R.includes)(`${namespace}${NAMESPACE_SEPARATOR}`), R.T ), keys ); const getValuesForKeys = R.map((key) => Storage.callMethod("getItem", key) ); return Promise.all(getValuesForKeys(filteredKeys)) .then(R.zipObj(filteredKeys)) .then( mapKeys( R.replace( `${namespace || DEFAULT_NAMESPACE}${NAMESPACE_SEPARATOR}`, "" ) ) ); }); } return { setItem, getItem, getAllItems, removeItem, }; }