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') {