UNPKG

@applicaster/zapp-react-dom-app

Version:

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

203 lines (175 loc) • 5.58 kB
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, namespace) { 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) { 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) { 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, ...args) { 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 {StorageI} */ export function getStorageModule(type) { 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, value, namespace = DEFAULT_NAMESPACE) { 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, namespace = DEFAULT_NAMESPACE) { 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, 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 = null) { 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, }; }