Introduce base/storage to represent the Web Storage API and persistence-related customizations

This commit is contained in:
Lyubo Marinov 2018-02-02 13:35:49 -06:00
parent 83243d5980
commit d7dddb2509
16 changed files with 93 additions and 72 deletions

View File

@ -14,11 +14,8 @@ import {
} from '../../base/participants'; } from '../../base/participants';
import { getProfile } from '../../base/profile'; import { getProfile } from '../../base/profile';
import { Fragment, RouteRegistry } from '../../base/react'; import { Fragment, RouteRegistry } from '../../base/react';
import { import { MiddlewareRegistry, ReducerRegistry } from '../../base/redux';
MiddlewareRegistry, import { PersistenceRegistry } from '../../base/storage';
PersistencyRegistry,
ReducerRegistry
} from '../../base/redux';
import { toURLString } from '../../base/util'; import { toURLString } from '../../base/util';
import { OverlayContainer } from '../../overlay'; import { OverlayContainer } from '../../overlay';
import { BlankPage } from '../../welcome'; import { BlankPage } from '../../welcome';
@ -349,7 +346,7 @@ export class AbstractApp extends Component {
return ( return (
createStore( createStore(
reducer, reducer,
PersistencyRegistry.getPersistedState(), PersistenceRegistry.getPersistedState(),
middleware)); middleware));
} }

View File

@ -4,7 +4,13 @@ import 'url-polyfill'; // Polyfill for URL constructor
import { Platform } from '../../react'; import { Platform } from '../../react';
import Storage from './Storage'; // XXX The library lib-jitsi-meet utilizes window.localStorage at the time of
// this writing and, consequently, the browser-related polyfills implemented
// here by the feature base/lib-jitsi-meet for the purposes of the library
// lib-jitsi-meet are incomplete without the Web Storage API! Should the library
// lib-jitsi-meet (and its dependencies) stop utilizing window.localStorage,
// the following import may be removed:
import '../../storage';
/** /**
* Gets the first common prototype of two specified Objects (treating the * Gets the first common prototype of two specified Objects (treating the
@ -271,11 +277,6 @@ function _visitNode(node, callback) {
global.document = document; global.document = document;
} }
// localStorage
if (typeof global.localStorage === 'undefined') {
global.localStorage = new Storage('@jitsi-meet/');
}
// location // location
if (typeof global.location === 'undefined') { if (typeof global.location === 'undefined') {
global.location = { global.location = {
@ -332,15 +333,6 @@ function _visitNode(node, callback) {
navigator.userAgent = userAgent; navigator.userAgent = userAgent;
} }
// sessionStorage
//
// Required by:
// - herment
// - Strophe
if (typeof global.sessionStorage === 'undefined') {
global.sessionStorage = new Storage();
}
// WebRTC // WebRTC
require('./polyfills-webrtc'); require('./polyfills-webrtc');
require('react-native-callstats/csio-polyfill'); require('react-native-callstats/csio-polyfill');

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { PersistencyRegistry, ReducerRegistry } from '../redux'; import { ReducerRegistry } from '../redux';
import { PersistenceRegistry } from '../storage';
import { PROFILE_UPDATED } from './actionTypes'; import { PROFILE_UPDATED } from './actionTypes';
@ -10,7 +11,10 @@ const DEFAULT_STATE = {
const STORE_NAME = 'features/base/profile'; const STORE_NAME = 'features/base/profile';
PersistencyRegistry.register(STORE_NAME, { /**
* Sets up the persistence of the feature base/profile.
*/
PersistenceRegistry.register(STORE_NAME, {
profile: true profile: true
}); });

View File

@ -1,6 +1,3 @@
export * from './functions'; export * from './functions';
export { default as MiddlewareRegistry } from './MiddlewareRegistry'; export { default as MiddlewareRegistry } from './MiddlewareRegistry';
export { default as PersistencyRegistry } from './PersistencyRegistry';
export { default as ReducerRegistry } from './ReducerRegistry'; export { default as ReducerRegistry } from './ReducerRegistry';
import './middleware';

View File

@ -1,30 +0,0 @@
Jitsi Meet - redux state persistency
====================================
Jitsi Meet has a persistency layer that persists a subtree (or specific subtrees) into window.localStorage (on web) or
AsyncStorage (on mobile).
Usage
=====
If a subtree of the redux store should be persisted (e.g. ``'features/base/profile'``), then persistency for that
subtree should be requested by registering the subtree (and related config) into PersistencyRegistry.
E.g. to register the field ``profile`` of the Redux subtree ``'features/base/profile'`` to be persisted, use:
```JavaScript
PersistencyRegistry.register('features/base/profile', {
profile: true
});
```
in the ``reducer.js`` of the ``profile`` feature.
When it's done, Jitsi Meet will automatically persist these subtrees/fields and rehidrate them on startup.
Throttling
==========
To avoid too frequent write operations in the storage, we utilise throttling in the persistency layer, meaning that the storage
gets persisted only once in every 2 seconds, even if multiple redux state changes occur during this period. This throttling timeout
can be configured in
```
react/features/base/redux/middleware.js#PERSIST_DELAY
```

View File

@ -19,13 +19,13 @@ declare type PersistencyConfigMap = { [name: string]: Object };
* A registry to allow features to register their redux store subtree to be * A registry to allow features to register their redux store subtree to be
* persisted and also handles the persistency calls too. * persisted and also handles the persistency calls too.
*/ */
class PersistencyRegistry { class PersistenceRegistry {
_checksum: string; _checksum: string;
_elements: PersistencyConfigMap; _elements: PersistencyConfigMap;
/** /**
* Initializes a new {@ code PersistencyRegistry} instance. * Initializes a new {@ code PersistenceRegistry} instance.
*/ */
constructor() { constructor() {
this._elements = {}; this._elements = {};
@ -166,4 +166,4 @@ class PersistencyRegistry {
} }
} }
export default new PersistencyRegistry(); export default new PersistenceRegistry();

View File

@ -0,0 +1,33 @@
Jitsi Meet - redux state persistence
====================================
Jitsi Meet has a persistence layer that persists specific subtrees of the redux
store/state into window.localStorage (on Web) or AsyncStorage (on mobile).
Usage
=====
If a subtree of the redux store should be persisted (e.g.
`'features/base/profile'`), then persistence for that subtree should be
requested by registering the subtree with `PersistenceRegistry`.
For example, to register the field `profile` of the redux subtree
`'features/base/profile'` to be persisted, use:
```javascript
PersistenceRegistry.register('features/base/profile', {
profile: true
});
```
in the `reducer.js` of the `base/profile` feature.
When it's done, Jitsi Meet will automatically persist these subtrees and
rehydrate them on startup.
Throttling
==========
To avoid too frequent write operations in the storage, we utilize throttling in
the persistence layer, meaning that the storage gets persisted only once every 2
seconds, even if multiple redux state changes occur during this period. The
throttling timeout can be configured in
```
react/features/base/storage/middleware.js#PERSIST_STATE_DELAY
```

View File

@ -0,0 +1 @@
export * from './native';

View File

View File

@ -0,0 +1,4 @@
export * from './_';
export { default as PersistenceRegistry } from './PersistenceRegistry';
import './middleware';

View File

@ -2,13 +2,13 @@
import _ from 'lodash'; import _ from 'lodash';
import { toState } from './functions'; import { MiddlewareRegistry, toState } from '../redux';
import MiddlewareRegistry from './MiddlewareRegistry';
import PersistencyRegistry from './PersistencyRegistry'; import PersistenceRegistry from './PersistenceRegistry';
/** /**
* The delay that passes between the last state change and the persisting of * The delay in milliseconds that passes between the last state change and the
* that state in the storage. * persisting of that state in the storage.
*/ */
const PERSIST_STATE_DELAY = 2000; const PERSIST_STATE_DELAY = 2000;
@ -17,21 +17,23 @@ const PERSIST_STATE_DELAY = 2000;
*/ */
const throttledPersistState const throttledPersistState
= _.throttle( = _.throttle(
state => PersistencyRegistry.persistState(state), state => PersistenceRegistry.persistState(state),
PERSIST_STATE_DELAY); PERSIST_STATE_DELAY);
/** /**
* A master MiddleWare to selectively persist state. Please use the * A master MiddleWare to selectively persist state. Please use the
* {@link persisterconfig.json} to set which subtrees of the Redux state should * {@link persisterconfig.json} to set which subtrees of the redux state should
* be persisted. * be persisted.
* *
* @param {Store} store - The redux store. * @param {Store} store - The redux store.
* @returns {Function} * @returns {Function}
*/ */
MiddlewareRegistry.register(store => next => action => { MiddlewareRegistry.register(store => next => action => {
const oldState = toState(store);
const result = next(action); const result = next(action);
const newState = toState(store);
throttledPersistState(toState(store)); oldState === newState || throttledPersistState(newState);
return result; return result;
}); });

View File

@ -0,0 +1 @@
import './polyfills-browser';

View File

@ -0,0 +1,19 @@
import Storage from './Storage';
(global => {
// localStorage
if (typeof global.localStorage === 'undefined') {
global.localStorage = new Storage('@jitsi-meet/');
}
// sessionStorage
//
// Required by:
// - herment
// - Strophe
if (typeof global.sessionStorage === 'undefined') {
global.sessionStorage = new Storage();
}
})(global || window || this); // eslint-disable-line no-invalid-this

View File

@ -46,7 +46,7 @@ export default class AbstractRecentList extends Component<Props> {
} }
/** /**
* Maps Redux state to component props. * Maps redux state to component props.
* *
* @param {Object} state - The redux state. * @param {Object} state - The redux state.
* @returns {{ * @returns {{

View File

@ -1,6 +1,7 @@
// @flow // @flow
import { PersistencyRegistry, ReducerRegistry } from '../base/redux'; import { ReducerRegistry } from '../base/redux';
import { PersistenceRegistry } from '../base/storage';
import { import {
STORE_CURRENT_CONFERENCE, STORE_CURRENT_CONFERENCE,
@ -30,14 +31,14 @@ export const MAX_LIST_SIZE = 30;
const STORE_NAME = 'features/recent-list'; const STORE_NAME = 'features/recent-list';
/** /**
* Registers the redux store subtree of this feature for persistency. * Sets up the persistence of the feature recent-list.
*/ */
PersistencyRegistry.register(STORE_NAME, { PersistenceRegistry.register(STORE_NAME, {
list: true list: true
}); });
/** /**
* Reduces the redux actions of the feature features/recent-list. * Reduces the redux actions of the feature recent-list.
*/ */
ReducerRegistry.register( ReducerRegistry.register(
STORE_NAME, STORE_NAME,