[RN] Prepare to polyfill sessionStorage

This commit is contained in:
Lyubo Marinov 2017-09-05 13:56:33 -05:00
parent bfeaf329e1
commit bf523711df
3 changed files with 162 additions and 114 deletions

View File

@ -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);
}
}

View File

@ -0,0 +1,158 @@
/* @flow */
import { AsyncStorage } from 'react-native';
/**
* A Web Sorage API implementation used for polyfilling
* <tt>window.localStorage</tt> and/or <tt>window.sessionStorage</tt>.
* <p>
* The Web Storage API is synchronous whereas React Native's builtin generic
* storage API <tt>AsyncStorage</tt> 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<string, string>;
_keyPrefix: ?string;
/**
* Initializes a new <tt>Storage</tt> instance. Loads all previously
* persisted data items from React Native's <tt>AsyncStorage</tt> if
* necessary.
*
* @param {string|undefined} keyPrefix - The prefix of the
* <tt>AsyncStorage</tt> 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 <tt>AsyncStorage</tt> keys persisted by this
* storage. If <tt>undefined</tt>, 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 <tt>key</tt> or
* <tt>null</tt>.
*/
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 <tt>key</tt>.
* @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);
}
}

View File

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