@applicaster/zapp-react-dom-app
Version:
Zapp App Component for Applicaster's Quick Brick React Native App
203 lines (175 loc) • 5.58 kB
JavaScript
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,
};
}