UNPKG

@applicaster/zapp-react-native-utils

Version:

Applicaster Zapp React Native utilities package

391 lines (330 loc) • 9.91 kB
import * as R from "ramda"; import { StorageLevel, StorageType } from "./consts"; import { getNamespaceAndKey, keyIsValid, buildNamespaceKey, savingResultIsSuccess, } from "./utils"; import { utilsLogger } from "../../logger"; import { sessionStorage as SessionStorage, localStorage as LocalStorage, secureStorage as SecureStorage, } from "./storage"; export { StorageLevel }; type KeyNameObj = { namespace: string; key: string; }; type KeyName = string | KeyNameObj; type ParsedKeyAndNamespace = { namespace?: string; key?: string; isValid: boolean; }; export const NOTHING = null; type Value = string | number | boolean | Array<any> | Object; type ValueOrNothing = Value | typeof NOTHING; type KeyProp = { key: KeyName; value: Value; storageLevel?: StorageLevel; }; const UtilsLogger = utilsLogger.addSubsystem("ContextKeysManager"); type Logger = { warn: Function; }; type Storage = { getItem: (key: string, namespace: string) => Promise<Value>; setItem: (key: string, value: Value, namespace: string) => Promise<boolean>; removeItem: (key: string, namespace: string) => Promise<boolean>; }; type Setup = { logger?: Logger; sessionStorage?: Storage; localStorage?: Storage; secureStorage?: Storage; referenceStorage?: Storage; }; export const REFERENCE_NAMESPACE = "applicaster.v2.references"; export class ContextKeysManager { private static _instance: ContextKeysManager; private _logger: Logger; private _sessionStorage: Storage; private _localStorage: Storage; private _secureStorage: Storage; private _referenceStorage: Storage; public static get instance() { if (!this._instance) { const instance = new ContextKeysManager({ logger: UtilsLogger, sessionStorage: SessionStorage, localStorage: LocalStorage, secureStorage: SecureStorage, referenceStorage: SessionStorage, }); this._instance = instance; } return this._instance; } // in case you need to tune dependency - use constructor constructor({ logger = UtilsLogger, sessionStorage = SessionStorage, localStorage = LocalStorage, secureStorage = SecureStorage, referenceStorage = SessionStorage, }: Setup) { this._logger = logger; this._sessionStorage = sessionStorage; this._localStorage = localStorage; this._secureStorage = secureStorage; this._referenceStorage = referenceStorage; } private parseKey(key: KeyName): ParsedKeyAndNamespace { const parsedKey = getNamespaceAndKey(key); if (!keyIsValid(parsedKey)) { this._logger.warn({ message: "Provided key is not valid - expecing an object with namespace and key properties", data: { key }, }); return { isValid: false }; } return { isValid: true, ...parsedKey }; } // GET public async getKeyByReference( key: string, namespace: string ): Promise<ValueOrNothing> { // 1) look for storage // 2) if storage found - look value in it // 3) if storage not found return NOTHING return this._referenceStorage .getItem(buildNamespaceKey(key, namespace), REFERENCE_NAMESPACE) .then((storage: StorageType | unknown) => { switch (storage) { case StorageType.session: return this._sessionStorage.getItem(key, namespace); case StorageType.local: return this._localStorage.getItem(key, namespace); case StorageType.secure: return this._secureStorage.getItem(key, namespace); default: // unknown storage return NOTHING; } }) .catch((error) => { // something went wrong this._logger.warn({ message: error.message, data: { key, namespace }, }); return NOTHING; }); } public async getKeys( keys: Array<KeyName> ): Promise<Map<KeyName, ValueOrNothing>> { const values = await Promise.all(keys.map((key) => this.getKey(key))); return new Map(R.zip(keys, values)); } public async getKey(key: KeyName): Promise<ValueOrNothing> { const { isValid: isValidKey, key: parsedKey, namespace: parsedNamespace, } = this.parseKey(key); if (!isValidKey) { // for invalid key - return nothing return NOTHING; } // at first - try to find by reference // if not found, look in session > local > secure const resultByReference = await this.getKeyByReference( parsedKey, parsedNamespace ); if (!R.isNil(resultByReference)) { return resultByReference; } // try to full-search try { const resultFromSessionStorage = await this._sessionStorage.getItem( parsedKey, parsedNamespace ); if (!R.isNil(resultFromSessionStorage)) { return resultFromSessionStorage; } const resultFromLocalStorage = await this._localStorage.getItem( parsedKey, parsedNamespace ); if (!R.isNil(resultFromLocalStorage)) { return resultFromLocalStorage; } const resultFromSecureStorage = await this._secureStorage.getItem( parsedKey, parsedNamespace ); if (!R.isNil(resultFromSecureStorage)) { return resultFromSecureStorage; } // nothing found return NOTHING; } catch (error) { // something went wrong this._logger.warn({ message: error.message, data: { key }, }); return NOTHING; } } // GET // SET public async setReferenceForKey( key: string, namespace: string, storageLevel: StorageLevel ): Promise<boolean> { return this._referenceStorage .setItem( buildNamespaceKey(key, namespace), storageLevel, REFERENCE_NAMESPACE ) .then((result) => { if (savingResultIsSuccess(result)) { return true; } this._logger.warn({ message: "While saving reference - reference storage return non expected value: true", data: { key, namespace, storageLevel }, }); return false; }) .catch((error) => { // something went wrong this._logger.warn({ message: error.message, data: { key, namespace, storageLevel }, }); return false; }); } // for all keys where saving were succeed return true // for others return false public async setKeys( keysProp: Array<KeyProp> ): Promise<Map<KeyName, boolean>> { const keys = keysProp.map((keyProp) => keyProp.key); const values = await Promise.all( keysProp.map((keyProp) => this.setKey(keyProp)) ); return new Map(R.zip(keys, values)); } // when succeed saving - return true // otherwise return false public async setKey({ key, value, storageLevel = StorageLevel.default, }: KeyProp): Promise<boolean> { const { isValid: isValidKey, key: parsedKey, namespace: parsedNamespace, } = this.parseKey(key); if (!isValidKey) { // for invalid key - return false return false; } try { switch (storageLevel) { case StorageLevel.persisted: return await Promise.all([ this._localStorage.setItem(parsedKey, value, parsedNamespace), this.setReferenceForKey(parsedKey, parsedNamespace, storageLevel), ]).then(([setItemResult]) => { return savingResultIsSuccess(setItemResult); }); case StorageLevel.secure: return await Promise.all([ this._secureStorage.setItem(parsedKey, value, parsedNamespace), this.setReferenceForKey(parsedKey, parsedNamespace, storageLevel), ]).then(([setItemResult]) => { return savingResultIsSuccess(setItemResult); }); default: return await Promise.all([ this._sessionStorage.setItem(parsedKey, value, parsedNamespace), this.setReferenceForKey(parsedKey, parsedNamespace, storageLevel), ]).then(([setItemResult]) => { return savingResultIsSuccess(setItemResult); }); } } catch (error) { // something went wrong this._logger.warn({ message: error.message, data: { key, value, storageLevel }, }); return false; } } // SET // REMOVE public async removeReferenceForKey( key: string, namespace: string ): Promise<boolean> { return this._referenceStorage .removeItem(buildNamespaceKey(key, namespace), REFERENCE_NAMESPACE) .then(() => true) .catch((error) => { // something went wrong this._logger.warn({ message: error.message, data: { key, namespace }, }); return false; }); } public async removeKey(key: KeyName): Promise<boolean> { const { isValid: isValidKey, key: parsedKey, namespace: parsedNamespace, } = this.parseKey(key); if (!isValidKey) { // for invalid key - return false return false; } return Promise.all([ this._sessionStorage.removeItem(parsedKey, parsedNamespace), this._localStorage.removeItem(parsedKey, parsedNamespace), this._secureStorage.removeItem(parsedKey, parsedNamespace), this.removeReferenceForKey(parsedKey, parsedNamespace), ]) .then(() => true) .catch((error) => { // something went wrong this._logger.warn({ message: error.message, data: { key }, }); return false; }); } public async removeKeys(keys: KeyName[]): Promise<Map<KeyName, boolean>> { const values = await Promise.all(keys.map((key) => this.removeKey(key))); return new Map(R.zip(keys, values)); } // REMOVE }