diff --git a/react/features/base/lib-jitsi-meet/native/LocalStorage.js b/react/features/base/lib-jitsi-meet/native/LocalStorage.js deleted file mode 100644 index 5e2b2d543..000000000 --- a/react/features/base/lib-jitsi-meet/native/LocalStorage.js +++ /dev/null @@ -1,112 +0,0 @@ -import { AsyncStorage } from 'react-native'; - -/** - * Prefix used to mark keys stored by our app in the global React Native - * storage. - */ -const JITSI_KEY_PREFIX = '@jitsi/'; - -/** - * Web Sorage API compatible object used for polyfilling window.localStorage. - * The Web Storage API is synchronous, whereas AsyncStorage, the builtin generic - * storage API in React Native, is asynchronous, so this object is optimistic: - * it will first store the value locally (in memory) so results can be served - * synchronously, and then save the value asynchronously. - * - * If any of the asynchronous operations produces an error, it's ignored. - */ -export default class LocalStorage { - /** - * Loads all keys from React Native's AsyncStorage. - */ - constructor() { - AsyncStorage.getAllKeys() - .then(keys => { - const jitsiKeys - = keys.filter(key => key.startsWith(JITSI_KEY_PREFIX)); - - AsyncStorage.multiGet(jitsiKeys) - .then(items => { - for (const item of items) { - const key = item[0].slice(JITSI_KEY_PREFIX.length); - const value = item[1]; - - this[key] = value; - } - }); - }); - } - - /** - * Gets the number of items stored. - * - * @returns {number} - */ - get length() { - return Object.keys(this).length; - } - - /** - * Removes all keys from the storage. - * - * @returns {void} - */ - clear() { - const keys = Object.keys(this); - - for (const key of keys) { - delete this[key]; - AsyncStorage.removeItem(`${JITSI_KEY_PREFIX}${key}`); - } - } - - /** - * Gets the element that was stored for the given `key`. - * - * @param {string} key - The requested `key`. - * @returns {string|null} - */ - getItem(key) { - if (this.hasOwnProperty(key)) { - return this[key]; - } - - return null; - } - - /** - * Gets the nth element in the storage. - * - * @param {number} n - The element number that is requested. - * @returns {string} - */ - key(n) { - return Object.keys(this)[n || 0]; - } - - /** - * Removes the given `key` from the storage. - * - * @param {string} key - The `key` which will be removed. - * @returns {void} - */ - removeItem(key) { - delete this[key]; - AsyncStorage.removeItem(`${JITSI_KEY_PREFIX}${key}`); - } - - /** - * Stores the given `value` for the given `key`. If a value or ready exists - * for that key, it's updated. - * - * @param {string} key - The key for the value which will be stored. - * @param {string} value - The value which will be stored. - * @returns {void} - */ - setItem(key, value) { - // eslint-disable-next-line no-param-reassign - value = String(value); - this[key] = value; - AsyncStorage.setItem(`${JITSI_KEY_PREFIX}${key}`, value); - } -} diff --git a/react/features/base/lib-jitsi-meet/native/Storage.js b/react/features/base/lib-jitsi-meet/native/Storage.js new file mode 100644 index 000000000..74d670868 --- /dev/null +++ b/react/features/base/lib-jitsi-meet/native/Storage.js @@ -0,0 +1,158 @@ +/* @flow */ + +import { AsyncStorage } from 'react-native'; + +/** + * A Web Sorage API implementation used for polyfilling + * window.localStorage and/or window.sessionStorage. + *

+ * The Web Storage API is synchronous whereas React Native's builtin generic + * storage API AsyncStorage is asynchronous so the implementation with + * persistence is optimistic: it will first store the value locally in memory so + * that results can be served synchronously and then persist the value + * asynchronously. If an asynchronous operation produces an error, it's ignored. + */ +export default class Storage { + _items: Map; + + _keyPrefix: ?string; + + /** + * Initializes a new Storage instance. Loads all previously + * persisted data items from React Native's AsyncStorage if + * necessary. + * + * @param {string|undefined} keyPrefix - The prefix of the + * AsyncStorage keys to be persisted by this storage. + */ + constructor(keyPrefix: ?string) { + /** + * The data items stored in this storage. + * + * @private + * @type {Map} + */ + this._items = new Map(); + + /** + * The prefix of the AsyncStorage keys persisted by this + * storage. If undefined, then the data items stored in this + * storage will not be persisted. + * + * @private + * @type {string} + */ + this._keyPrefix = keyPrefix; + + if (typeof this._keyPrefix !== 'undefined') { + // Load all previously persisted data items from React Native's + // AsyncStorage. + AsyncStorage.getAllKeys().then((...getAllKeysCallbackArgs) => { + // XXX The keys argument of getAllKeys' callback may or may not + // be preceded by an error argument. + const keys + = getAllKeysCallbackArgs[getAllKeysCallbackArgs.length - 1] + .filter(key => key.startsWith(this._keyPrefix)); + + AsyncStorage.multiGet(keys).then((...multiGetCallbackArgs) => { + // XXX The result argument of multiGet may or may not be + // preceded by an errors argument. + const result + = multiGetCallbackArgs[multiGetCallbackArgs.length - 1]; + const keyPrefixLength + = this._keyPrefix && this._keyPrefix.length; + + // eslint-disable-next-line prefer-const + for (let [ key, value ] of result) { + key = key.substring(keyPrefixLength); + + // XXX The loading of the previously persisted data + // items from AsyncStorage is asynchronous which means + // that it is technically possible to invoke setItem + // with a key before the key is loaded from + // AsyncStorage. + if (!this._items.has(key)) { + this._items.set(key, value); + } + } + }); + }); + } + } + + /** + * Removes all keys from this storage. + * + * @returns {void} + */ + clear() { + for (const key of this._items.keys()) { + this.removeItem(key); + } + } + + /** + * Returns the value associated with a specific key in this storage. + * + * @param {string} key - The name of the key to retrieve the value of. + * @returns {string|null} The value associated with key or + * null. + */ + getItem(key: string) { + return this._items.has(key) ? this._items.get(key) : null; + } + + /** + * Returns the name of the nth key in this storage. + * + * @param {number} n - The zero-based integer index of the key to get the + * name of. + * @returns {string} The name of the nth key in this storage. + */ + key(n: number) { + let i = 0; + + for (const key in this._items.keys()) { + if (i++ === n) { + return key; + } + } + } + + /** + * Returns an integer representing the number of data items stored in this + * storage. + * + * @returns {number} + */ + get length(): number { + return this._items.size; + } + + /** + * Removes a specific key from this storage. + * + * @param {string} key - The name of the key to remove. + * @returns {void} + */ + removeItem(key: string) { + this._items.delete(key); + typeof this._keyPrefix === 'undefined' + || AsyncStorage.removeItem(`${String(this._keyPrefix)}${key}`); + } + + /** + * Adds a specific key to this storage and associates it with a specific + * value. If the key exists already, updates its value. + * + * @param {string} key - The name of the key to add/update. + * @param {string} value - The value to associate with key. + * @returns {void} + */ + setItem(key: string, value: string) { + value = String(value); // eslint-disable-line no-param-reassign + this._items.set(key, value); + typeof this._keyPrefix === 'undefined' + || AsyncStorage.setItem(`${String(this._keyPrefix)}${key}`, value); + } +} diff --git a/react/features/base/lib-jitsi-meet/native/polyfills-browser.js b/react/features/base/lib-jitsi-meet/native/polyfills-browser.js index e4506eedf..4888cbd89 100644 --- a/react/features/base/lib-jitsi-meet/native/polyfills-browser.js +++ b/react/features/base/lib-jitsi-meet/native/polyfills-browser.js @@ -2,7 +2,7 @@ import Iterator from 'es6-iterator'; import BackgroundTimer from 'react-native-background-timer'; import 'url-polyfill'; // Polyfill for URL constructor -import LocalStorage from './LocalStorage'; +import Storage from './Storage'; /** * Gets the first common prototype of two specified Objects (treating the @@ -267,7 +267,9 @@ function _visitNode(node, callback) { } // localStorage - global.localStorage = new LocalStorage(); + if (typeof global.localStorage === 'undefined') { + global.localStorage = new Storage('@jitsi-meet/'); + } // location if (typeof global.location === 'undefined') {