| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- // @flow
-
- import Logger from 'jitsi-meet-logger';
- import md5 from 'js-md5';
-
- const logger = Logger.getLogger(__filename);
-
- /**
- * The name of the localStorage store where the app persists its values to.
- */
- const PERSISTED_STATE_NAME = 'jitsi-state';
-
- /**
- * Mixed type of the element (subtree) config. If it's a boolean,
- * (and is true) we persist the entire subtree. If it's an object,
- * we perist a filtered subtree based on the properties in the
- * config object.
- */
- declare type ElementConfig = Object | boolean;
-
- /**
- * The type of the name-config pairs stored in this reducer.
- */
- declare type PersistencyConfigMap = { [name: string]: ElementConfig };
-
- /**
- * A registry to allow features to register their redux store subtree to be
- * persisted and also handles the persistency calls too.
- */
- class PersistenceRegistry {
- _checksum: string;
-
- _elements: PersistencyConfigMap;
-
- /**
- * Initializes a new {@ code PersistenceRegistry} instance.
- */
- constructor() {
- this._elements = {};
- }
-
- /**
- * Returns the persisted redux state. This function takes the
- * {@link #_elements} into account as we may have persisted something in the
- * past that we don't want to retreive anymore. The next
- * {@link #persistState} will remove those values.
- *
- * @returns {Object}
- */
- getPersistedState() {
- let filteredPersistedState = {};
- let persistedState = window.localStorage.getItem(PERSISTED_STATE_NAME);
-
- if (persistedState) {
- // This is the legacy implementation,
- // must be removed in a later version.
- try {
- persistedState = JSON.parse(persistedState);
- } catch (error) {
- logger.error(
- 'Error parsing persisted state',
- persistedState,
- error);
- persistedState = {};
- }
-
- filteredPersistedState
- = this._getFilteredState(persistedState);
-
- // legacy values must be written to the new store format and
- // old values to be deleted, so then it'll never be used again.
- this.persistState(filteredPersistedState);
- window.localStorage.removeItem(PERSISTED_STATE_NAME);
- } else {
- // new, split-keys implementation
- for (const subtreeName of Object.keys(this._elements)) {
- /*
- * this assumes that the persisted value is stored under the
- * same key as the feature's redux state name.
- * We'll need to introduce functions later that can control
- * the persist key's name. Similar to control serialization
- * and deserialization.
- * But that should be a straightforward change.
- */
- const persistedSubtree
- = this._getPersistedSubtree(
- subtreeName,
- this._elements[subtreeName]
- );
-
- if (persistedSubtree !== undefined) {
- filteredPersistedState[subtreeName] = persistedSubtree;
- }
- }
- }
-
- // initialize checksum
- this._checksum = this._calculateChecksum(filteredPersistedState);
-
- this._checksum = this._calculateChecksum(filteredPersistedState);
- logger.info('redux state rehydrated as', filteredPersistedState);
-
- return filteredPersistedState;
- }
-
- /**
- * Initiates a persist operation, but its execution will depend on the
- * current checksums (checks changes).
- *
- * @param {Object} state - The redux state.
- * @returns {void}
- */
- persistState(state: Object) {
- const filteredState = this._getFilteredState(state);
- const newCheckSum = this._calculateChecksum(filteredState);
-
- if (newCheckSum !== this._checksum) {
- for (const subtreeName of Object.keys(filteredState)) {
- try {
- window.localStorage.setItem(
- subtreeName,
- JSON.stringify(filteredState[subtreeName]));
- } catch (error) {
- logger.error('Error persisting redux subtree',
- subtreeName,
- filteredState[subtreeName],
- error
- );
- }
- }
- logger.info(
- `redux state persisted. ${this._checksum} -> ${
- newCheckSum}`);
- this._checksum = newCheckSum;
- }
- }
-
- /**
- * Registers a new subtree config to be used for the persistency.
- *
- * @param {string} name - The name of the subtree the config belongs to.
- * @param {ElementConfig} config - The config object, or boolean
- * if the entire subtree needs to be persisted.
- * @returns {void}
- */
- register(name: string, config?: ElementConfig = true) {
- this._elements[name] = config;
- }
-
- /**
- * Calculates the checksum of the current or the new values of the state.
- *
- * @private
- * @param {Object} filteredState - The filtered/persisted redux state.
- * @returns {string}
- */
- _calculateChecksum(filteredState: Object) {
- try {
- return md5.hex(JSON.stringify(filteredState) || '');
- } catch (error) {
- logger.error(
- 'Error calculating checksum for state',
- filteredState,
- error);
-
- return '';
- }
- }
-
- /**
- * Retreives a persisted subtree from the storage.
- *
- * @private
- * @param {string} subtreeName - The name of the subtree.
- * @param {Object} subtreeConfig - The config of the subtree
- * from this._elements.
- * @returns {Object}
- */
- _getPersistedSubtree(subtreeName, subtreeConfig) {
- let persistedSubtree = window.localStorage.getItem(subtreeName);
-
- if (persistedSubtree) {
- try {
- persistedSubtree = JSON.parse(persistedSubtree);
- const filteredSubtree
- = this._getFilteredSubtree(persistedSubtree, subtreeConfig);
-
- if (filteredSubtree !== undefined) {
- return filteredSubtree;
- }
- } catch (error) {
- logger.error(
- 'Error parsing persisted subtree',
- subtreeName,
- persistedSubtree,
- error);
- }
- }
-
- return null;
- }
-
- /**
- * Prepares a filtered state from the actual or the persisted redux state,
- * based on this registry.
- *
- * @private
- * @param {Object} state - The actual or persisted redux state.
- * @returns {Object}
- */
- _getFilteredState(state: Object) {
- const filteredState = {};
-
- for (const name of Object.keys(this._elements)) {
- if (state[name]) {
- filteredState[name] = this._getFilteredSubtree(
- state[name],
- this._elements[name]);
- }
- }
-
- return filteredState;
- }
-
- /**
- * Prepares a filtered subtree based on the config for persisting or for
- * retrieval.
- *
- * @private
- * @param {Object} subtree - The redux state subtree.
- * @param {ElementConfig} subtreeConfig - The related config.
- * @returns {Object}
- */
- _getFilteredSubtree(subtree, subtreeConfig) {
- let filteredSubtree;
-
- if (subtreeConfig === true) {
- // we persist the entire subtree
- filteredSubtree = subtree;
- } else if (typeof subtreeConfig === 'object') {
- // only a filtered subtree gets persisted, based on the
- // subtreeConfig object.
- filteredSubtree = {};
- for (const persistedKey of Object.keys(subtree)) {
- if (subtreeConfig[persistedKey]) {
- filteredSubtree[persistedKey] = subtree[persistedKey];
- }
- }
- }
-
- return filteredSubtree;
- }
- }
-
- export default new PersistenceRegistry();
|